feat(frontend): tabler-iconsのサブセット化 (#15340)

* feat(frontend): tabler-iconsの使用されていないアイコンを削除するように

* fix

* fix

* fix

* fix

* fix

* Update Changelog

* enhance: tablerのCSSを使用されているクラスのみに限定

* 使用するアイコンパッケージをそろえる

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* spdx

* typo

* fix: サブセットから除外される書き方をしている部分を修正

* fix: 同じunicodeに複数のアイコンclassが割り当てられている場合に対応

* remove debug code

* Update CHANGELOG.md

* fix merge error

* setup renovate

* fix: woff2ではなくwoffに変換していたのを修正

* update deps

* update changelog
This commit is contained in:
かっこかり 2025-05-22 22:56:38 +09:00 committed by GitHub
parent c2478e5877
commit e6e8bfa591
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 457 additions and 23 deletions

View File

@ -58,6 +58,7 @@ jobs:
"packages/frontend/test" "packages/frontend/test"
"packages/frontend-embed/@types" "packages/frontend-embed/@types"
"packages/frontend-embed/src" "packages/frontend-embed/src"
"packages/icons-subsetter/src"
"packages/misskey-bubble-game/src" "packages/misskey-bubble-game/src"
"packages/misskey-reversi/src" "packages/misskey-reversi/src"
"packages/sw/src" "packages/sw/src"

View File

@ -12,6 +12,7 @@
- モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます - モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます
- 「全て公開(今までの挙動)」「ローカルのコンテンツだけ公開(=サーバー内で受信されたリモートのコンテンツは公開しない)」「何も公開しない」から選択できます - 「全て公開(今までの挙動)」「ローカルのコンテンツだけ公開(=サーバー内で受信されたリモートのコンテンツは公開しない)」「何も公開しない」から選択できます
- デフォルト値は「ローカルのコンテンツだけ公開」になっています - デフォルト値は「ローカルのコンテンツだけ公開」になっています
- Enhance: UIのアイコンデータの読み込みを軽量化
### Client ### Client
- Feat: ドライブのUIが強化されました - Feat: ドライブのUIが強化されました

View File

@ -258,6 +258,12 @@ Misskey uses Vue(v3) as its front-end framework.
- **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.** - **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.**
- Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. - Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
## Tabler Icons
アイコンは、Production Build時に使用されていないものが削除されるようになっています。
**アイコンを動的に設定する際には、 `ti-${someVal}` のような、アイコン名のみを動的に変化させる実装を行わないでください。**
必ず `ti-xxx` のような完全なクラス名を含めるようにしてください。
## nirax ## nirax
niraxは、Misskeyで使用しているオリジナルのフロントエンドルーティングシステムです。 niraxは、Misskeyで使用しているオリジナルのフロントエンドルーティングシステムです。
**vue-routerから影響を多大に受けているので、まずはvue-routerについて学ぶことをお勧めします。** **vue-routerから影響を多大に受けているので、まずはvue-routerについて学ぶことをお勧めします。**

View File

@ -22,6 +22,7 @@ COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"] COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"]
COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"] COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"]
COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"]
COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/sw/package.json", "./packages/sw/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]

View File

@ -11,6 +11,7 @@
"packages/frontend-shared", "packages/frontend-shared",
"packages/frontend", "packages/frontend",
"packages/frontend-embed", "packages/frontend-embed",
"packages/icons-subsetter",
"packages/backend", "packages/backend",
"packages/sw", "packages/sw",
"packages/misskey-js", "packages/misskey-js",

View File

@ -14,13 +14,13 @@
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.4", "@rollup/pluginutils": "5.1.4",
"@tabler/icons-webfont": "3.33.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4", "@vitejs/plugin-vue": "5.2.4",
"@vue/compiler-sfc": "3.5.14", "@vue/compiler-sfc": "3.5.14",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"icons-subsetter": "workspace:*",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
"json5": "2.2.3", "json5": "2.2.3",
"mfm-js": "0.24.0", "mfm-js": "0.24.0",
@ -39,6 +39,7 @@
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.1", "@misskey-dev/summaly": "5.2.1",
"@tabler/icons-webfont": "3.33.0",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.7", "@types/estree": "1.0.7",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",

View File

@ -6,7 +6,11 @@
// https://vitejs.dev/config/build-options.html#build-modulepreload // https://vitejs.dev/config/build-options.html#build-modulepreload
import 'vite/modulepreload-polyfill'; import 'vite/modulepreload-polyfill';
import '@tabler/icons-webfont/dist/tabler-icons.scss'; if (import.meta.env.DEV) {
await import('@tabler/icons-webfont/dist/tabler-icons.scss');
} else {
await import('icons-subsetter/built/tabler-icons-frontendEmbed.css');
}
import '@/style.scss'; import '@/style.scss';
import { createApp, defineAsyncComponent } from 'vue'; import { createApp, defineAsyncComponent } from 'vue';

View File

@ -26,7 +26,6 @@
"@rollup/pluginutils": "5.1.4", "@rollup/pluginutils": "5.1.4",
"@sentry/vue": "9.22.0", "@sentry/vue": "9.22.0",
"@syuilo/aiscript": "0.19.0", "@syuilo/aiscript": "0.19.0",
"@tabler/icons-webfont": "3.33.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4", "@vitejs/plugin-vue": "5.2.4",
"@vue/compiler-sfc": "3.5.14", "@vue/compiler-sfc": "3.5.14",
@ -48,6 +47,7 @@
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
"icons-subsetter": "workspace:*",
"idb-keyval": "6.2.2", "idb-keyval": "6.2.2",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
@ -99,6 +99,7 @@
"@storybook/types": "8.6.14", "@storybook/types": "8.6.14",
"@storybook/vue3": "8.6.14", "@storybook/vue3": "8.6.14",
"@storybook/vue3-vite": "8.6.14", "@storybook/vue3-vite": "8.6.14",
"@tabler/icons-webfont": "3.33.0",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.7", "@types/estree": "1.0.7",

View File

@ -6,7 +6,11 @@
// https://vitejs.dev/config/build-options.html#build-modulepreload // https://vitejs.dev/config/build-options.html#build-modulepreload
import 'vite/modulepreload-polyfill'; import 'vite/modulepreload-polyfill';
import '@tabler/icons-webfont/dist/tabler-icons.scss'; if (import.meta.env.DEV) {
await import('@tabler/icons-webfont/dist/tabler-icons.scss');
} else {
await import('icons-subsetter/built/tabler-icons-frontend.css');
}
import '@/style.scss'; import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js'; import { mainBoot } from '@/boot/main-boot.js';

View File

@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.uploadFolder }} {{ i18n.ts.uploadFolder }}
</div> </div>
<button v-if="selectMode" class="_button" :class="$style.checkboxWrapper" @click.prevent.stop="checkboxClicked"> <button v-if="selectMode" class="_button" :class="$style.checkboxWrapper" @click.prevent.stop="checkboxClicked">
<div :class="[$style.checkbox, { [$style.checked]: isSelected }]"></div> <div :class="[$style.checkbox, { [$style.checked]: isSelected, 'ti ti-check': isSelected }]"></div>
</button> </button>
</div> </div>
</template> </template>
@ -368,16 +368,14 @@ function onContextmenu(ev: MouseEvent) {
border-color: var(--MI_THEME-accent); border-color: var(--MI_THEME-accent);
background: var(--MI_THEME-accent); background: var(--MI_THEME-accent);
&::after { &::before {
content: "\ea5e";
font-family: 'tabler-icons';
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
color: #fff; color: #fff;
font-size: 12px; font-size: 12px;
line-height: 22px; line-height: 18px;
} }
} }
} }

View File

@ -0,0 +1,15 @@
## これは何
フロントエンドの各パッケージで使用されているtabler iconsのclassをスキャンし、使用されているiconのみを抽出するツールです。
なお、サブセット版に無いアイコンが呼び出された場合は本物のtabler icons フォントにフォールバックするようになっています。
このツールは本番ビルド時にのみ使用されます開発モードでも最初の1回だけビルドが走りますが、これは型エラーを抑制するためにファイルを置いておく用の措置です
現時点では `src/generator.ts``filesToScan` にスキャン対象のファイルが書かれています。もしこれに当てはまらないファイルをサブセットのスキャン対象とする場合はこの部分を適宜修正してください。
## 使い方
```bash
pnpm build
```

View File

@ -0,0 +1,18 @@
import tsParser from '@typescript-eslint/parser';
import sharedConfig from '../shared/eslint.config.js';
// eslint-disable-next-line import/no-default-export
export default [
...sharedConfig,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
parser: tsParser,
project: ['./tsconfig.json'],
sourceType: 'module',
tsconfigRootDir: import.meta.dirname,
},
},
},
];

View File

@ -0,0 +1,30 @@
{
"name": "icons-subsetter",
"version": "0.0.0",
"private": true,
"description": "Subset tabler-icons webfont",
"type": "module",
"scripts": {
"build": "tsx src/generator.ts",
"eslint": "eslint src/**/*.ts",
"typecheck": "tsc --noEmit",
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "22.15.21",
"@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1"
},
"dependencies": {
"@tabler/icons-webfont": "3.33.0",
"harfbuzzjs": "0.4.7",
"tiny-glob": "0.2.9",
"tsx": "4.19.4",
"typescript": "5.8.3",
"wawoff2": "2.0.1"
},
"files": [
"built"
]
}

View File

@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { promises as fsp, existsSync } from 'fs';
import path from 'path';
import glob from 'tiny-glob';
import { generateSubsettedFont } from './subsetter.js';
const filesToScan = {
frontend: 'packages/frontend/src/**/*.{ts,vue}',
//frontendShared: 'packages/frontend-shared/js/**/*.{ts}', // 現時点では該当がないのでスキップ。ここをコメントアウトするときは、各フロントエンドにこのチャンクのCSSのimportを追加すること
frontendEmbed: 'packages/frontend-embed/src/**/*.{ts,vue}',
};
async function main() {
const start = performance.now();
// 1. ビルドディレクトリを削除
if (existsSync('./built')) {
await fsp.rm('./built', { recursive: true });
}
await fsp.mkdir('./built');
// 2. tabler-icons.min.cssから、class名とUnicodeのマッピングを抽出
const css = await fsp.readFile('node_modules/@tabler/icons-webfont/dist/tabler-icons.min.css', 'utf-8');
const cssRegex = /\.(ti-[a-z0-9-]+)::?before\s*{\n?\s*content:\s*["']\\([a-fA-F0-9]+)["'];?\n?\s*}/g;
const rgMap = new Map<string, string>();
let matches: RegExpExecArray | null;
while ((matches = cssRegex.exec(css)) !== null) {
rgMap.set(matches[1], matches[2]);
}
// 3. tabler-icons-classes.cssから、.tiのルールを抽出
const classTiBaseRule = css.match(/\.ti\s*{[^}]*}/)![0];
// 4. フォールバック用のtabler-icons.woff2をコピー
const fontPath = 'node_modules/@tabler/icons-webfont/dist/fonts/';
await fsp.copyFile(fontPath + 'tabler-icons.woff2', './built/tabler-icons.woff2');
// 5. 各チャンクごとにファイルをスキャンして、使用されているアイコンを抽出
const unicodeRangeValues = new Map<string, number[]>();
for (const [key, dir] of Object.entries(filesToScan)) {
console.log(`Scanning ${key}...`);
const iconsToPack = new Set<string>();
const cwd = path.resolve(process.cwd(), '../../');
const files = await glob(dir, { cwd });
for (const file of files) {
//console.log(`Scanning ${file}`);
const content = await fsp.readFile(path.resolve(cwd, file), 'utf-8');
const classRegex = /ti-[a-z0-9-]+/g;
let matches: RegExpExecArray | null;
while ((matches = classRegex.exec(content)) !== null) {
const icon = matches[0];
if (rgMap.has(icon)) {
iconsToPack.add(icon);
}
}
}
// 6. チャンク内で使用されているアイコンのUnicodeの配列を生成
const unicodeValues = Array.from(iconsToPack).map((icon) => parseInt(rgMap.get(icon)!, 16));
unicodeRangeValues.set(key, unicodeValues);
}
// 7. Tabler Iconフォントをサブセット化
const subsettedFonts = await generateSubsettedFont(fontPath + 'tabler-icons.ttf', unicodeRangeValues);
// 8. サブセット化したフォント・CSSを書き出し
await Promise.allSettled(Array.from(subsettedFonts.entries()).map(async ([key, buffer]) => {
const cssRules = [`@font-face {
font-family: "tabler-icons";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("./tabler-icons.woff2") format("woff2");
}`];
// サブセット化したフォントの中身があるunicodeRangeValuesの配列が空ではない場合のみ、サブセットしたものに関する情報を追記
if (unicodeRangeValues.get(key)!.length > 0) {
await fsp.writeFile(`./built/tabler-icons-${key}.woff2`, buffer);
const unicodeRangeString = (() => {
const values = unicodeRangeValues.get(key)!.sort((a, b) => a - b);
const ranges = [];
for (let i = 0; i < values.length; i++) {
const start = values[i];
let end = values[i];
while (values[i + 1] === end + 1) {
end = values[i + 1];
i++;
}
if (start === end) {
ranges.push(`U+${start.toString(16)}`);
} else if (start + 1 === end) {
ranges.push(`U+${start.toString(16)}`, `U+${end.toString(16)}`);
} else {
ranges.push(`U+${start.toString(16)}-${end.toString(16)}`);
}
}
return ranges.join(', ');
})();
cssRules.push(`@font-face {
font-family: "tabler-icons";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("./tabler-icons-${key}.woff2") format("woff2");
unicode-range: ${unicodeRangeString};
}`);
cssRules.push(classTiBaseRule);
// 使用されているアイコンのclassとの対応を追記
for (const icon of unicodeRangeValues.get(key)!) {
const iconClasses = Array.from(rgMap.entries()).filter(([_, unicode]) => parseInt(unicode, 16) === icon);
if (iconClasses.length > 1) {
console.warn(`[WARN] Multiple classes for the same unicode: ${iconClasses.map(([cls]) => cls).join(', ')}. Maybe it's deprecated?`);
}
const iconSelector = iconClasses.map(([className]) => `.${className}::before`).join(', ');
cssRules.push(`${iconSelector} { content: "\\${icon.toString(16)}"; }`);
}
}
await fsp.writeFile(`./built/tabler-icons-${key}.css`, cssRules.join('\n') + '\n');
}));
const end = performance.now();
console.log(`Done in ${Math.round((end - start) * 100) / 100}ms`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { promises as fsp } from 'fs';
import { compress } from 'wawoff2';
export async function generateSubsettedFont(ttfPath: string, unicodeRangeValues: Map<string, number[]>) {
const ttf = await fsp.readFile(ttfPath);
const {
instance: { exports: harfbuzzWasm },
}: any = await WebAssembly.instantiate(await fsp.readFile('./node_modules/harfbuzzjs/hb-subset.wasm'));
const heapu8 = new Uint8Array(harfbuzzWasm.memory.buffer);
const subsetFonts = new Map<string, Buffer>();
let i = 0;
for (const [key, unicodeValues] of unicodeRangeValues) {
i++;
console.log(`Generating subset ${i} of ${unicodeRangeValues.size}...`);
// サブセット入力を作成
const input = harfbuzzWasm.hb_subset_input_create_or_fail();
if (input === 0) {
throw new Error('hb_subset_input_create_or_fail (harfbuzz) returned zero');
}
// フォントバッファにフォントデータをセット
const fontBuffer = harfbuzzWasm.malloc(ttf.byteLength);
heapu8.set(new Uint8Array(ttf), fontBuffer);
// フォントフェイスを作成
const blob = harfbuzzWasm.hb_blob_create(fontBuffer, ttf.byteLength, 2, 0, 0);
const face = harfbuzzWasm.hb_face_create(blob, 0);
harfbuzzWasm.hb_blob_destroy(blob);
// Unicodeセットに指定されたUnicodeポイントを追加
const inputUnicodes = harfbuzzWasm.hb_subset_input_unicode_set(input);
for (const unicode of unicodeValues) {
harfbuzzWasm.hb_set_add(inputUnicodes, unicode);
}
// サブセットを作成
let subset;
try {
subset = harfbuzzWasm.hb_subset_or_fail(face, input);
if (subset === 0) {
harfbuzzWasm.hb_face_destroy(face);
harfbuzzWasm.free(fontBuffer);
throw new Error('hb_subset_or_fail (harfbuzz) returned zero');
}
} finally {
harfbuzzWasm.hb_subset_input_destroy(input);
}
// サブセットフォントデータを取得
const result = harfbuzzWasm.hb_face_reference_blob(subset);
const offset = harfbuzzWasm.hb_blob_get_data(result, 0);
const subsetByteLength = harfbuzzWasm.hb_blob_get_length(result);
if (subsetByteLength === 0) {
harfbuzzWasm.hb_face_destroy(face);
harfbuzzWasm.hb_blob_destroy(result);
harfbuzzWasm.free(fontBuffer);
throw new Error('hb_blob_get_length (harfbuzz) returned zero');
}
// サブセットフォントをバッファに格納
subsetFonts.set(key, Buffer.from(await compress(heapu8.slice(offset, offset + subsetByteLength))));
// メモリを解放
harfbuzzWasm.hb_blob_destroy(result);
harfbuzzWasm.hb_face_destroy(subset);
harfbuzzWasm.hb_face_destroy(face);
harfbuzzWasm.free(fontBuffer);
}
return subsetFonts;
}

View File

@ -0,0 +1,20 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
},
"include": [
"src/**/*.ts"
],
"exclude": []
}

120
pnpm-lock.yaml generated
View File

@ -728,9 +728,6 @@ importers:
'@syuilo/aiscript': '@syuilo/aiscript':
specifier: 0.19.0 specifier: 0.19.0
version: 0.19.0 version: 0.19.0
'@tabler/icons-webfont':
specifier: 3.33.0
version: 3.33.0
'@twemoji/parser': '@twemoji/parser':
specifier: 15.1.1 specifier: 15.1.1
version: 15.1.1 version: 15.1.1
@ -794,6 +791,9 @@ importers:
frontend-shared: frontend-shared:
specifier: workspace:* specifier: workspace:*
version: link:../frontend-shared version: link:../frontend-shared
icons-subsetter:
specifier: workspace:*
version: link:../icons-subsetter
idb-keyval: idb-keyval:
specifier: 6.2.2 specifier: 6.2.2
version: 6.2.2 version: 6.2.2
@ -942,6 +942,9 @@ importers:
'@storybook/vue3-vite': '@storybook/vue3-vite':
specifier: 8.6.14 specifier: 8.6.14
version: 8.6.14(storybook@8.6.14(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.5(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4))(vue@3.5.14(typescript@5.8.3)) version: 8.6.14(storybook@8.6.14(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.3.5(@types/node@22.15.21)(sass@1.89.0)(terser@5.39.2)(tsx@4.19.4))(vue@3.5.14(typescript@5.8.3))
'@tabler/icons-webfont':
specifier: 3.33.0
version: 3.33.0
'@testing-library/vue': '@testing-library/vue':
specifier: 8.1.0 specifier: 8.1.0
version: 8.1.0(@vue/compiler-sfc@3.5.14)(@vue/server-renderer@3.5.14(vue@3.5.14(typescript@5.8.3)))(vue@3.5.14(typescript@5.8.3)) version: 8.1.0(@vue/compiler-sfc@3.5.14)(@vue/server-renderer@3.5.14(vue@3.5.14(typescript@5.8.3)))(vue@3.5.14(typescript@5.8.3))
@ -1086,9 +1089,6 @@ importers:
'@rollup/pluginutils': '@rollup/pluginutils':
specifier: 5.1.4 specifier: 5.1.4
version: 5.1.4(rollup@4.41.0) version: 5.1.4(rollup@4.41.0)
'@tabler/icons-webfont':
specifier: 3.33.0
version: 3.33.0
'@twemoji/parser': '@twemoji/parser':
specifier: 15.1.1 specifier: 15.1.1
version: 15.1.1 version: 15.1.1
@ -1110,6 +1110,9 @@ importers:
frontend-shared: frontend-shared:
specifier: workspace:* specifier: workspace:*
version: link:../frontend-shared version: link:../frontend-shared
icons-subsetter:
specifier: workspace:*
version: link:../icons-subsetter
json5: json5:
specifier: 2.2.3 specifier: 2.2.3
version: 2.2.3 version: 2.2.3
@ -1156,6 +1159,9 @@ importers:
'@misskey-dev/summaly': '@misskey-dev/summaly':
specifier: 5.2.1 specifier: 5.2.1
version: 5.2.1 version: 5.2.1
'@tabler/icons-webfont':
specifier: 3.33.0
version: 3.33.0
'@testing-library/vue': '@testing-library/vue':
specifier: 8.1.0 specifier: 8.1.0
version: 8.1.0(@vue/compiler-sfc@3.5.14)(@vue/server-renderer@3.5.14(vue@3.5.14(typescript@5.8.3)))(vue@3.5.14(typescript@5.8.3)) version: 8.1.0(@vue/compiler-sfc@3.5.14)(@vue/server-renderer@3.5.14(vue@3.5.14(typescript@5.8.3)))(vue@3.5.14(typescript@5.8.3))
@ -1272,6 +1278,40 @@ importers:
specifier: 10.1.3 specifier: 10.1.3
version: 10.1.3(eslint@9.27.0) version: 10.1.3(eslint@9.27.0)
packages/icons-subsetter:
dependencies:
'@tabler/icons-webfont':
specifier: 3.33.0
version: 3.33.0
harfbuzzjs:
specifier: 0.4.7
version: 0.4.7
tiny-glob:
specifier: 0.2.9
version: 0.2.9
tsx:
specifier: 4.19.4
version: 4.19.4
typescript:
specifier: 5.8.3
version: 5.8.3
wawoff2:
specifier: 2.0.1
version: 2.0.1
devDependencies:
'@types/node':
specifier: 22.15.21
version: 22.15.21
'@types/wawoff2':
specifier: 1.0.2
version: 1.0.2
'@typescript-eslint/eslint-plugin':
specifier: 8.32.1
version: 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint@9.27.0)(typescript@5.8.3)
'@typescript-eslint/parser':
specifier: 8.32.1
version: 8.32.1(eslint@9.27.0)(typescript@5.8.3)
packages/misskey-bubble-game: packages/misskey-bubble-game:
dependencies: dependencies:
eventemitter3: eventemitter3:
@ -4487,6 +4527,9 @@ packages:
'@types/vary@1.1.3': '@types/vary@1.1.3':
resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==} resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==}
'@types/wawoff2@1.0.2':
resolution: {integrity: sha512-UVq4NxMuBywz5gklafg1HcENrK02trh/q+ifyvO6xktflGXRtf11GYsia3/l5nxg2edFQRlnUT6ivnk898WDiw==}
'@types/web-push@3.6.4': '@types/web-push@3.6.4':
resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==} resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==}
@ -6602,6 +6645,9 @@ packages:
get-tsconfig@4.10.0: get-tsconfig@4.10.0:
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
getos@3.2.1: getos@3.2.1:
resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==}
@ -6657,10 +6703,16 @@ packages:
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
globalyzer@0.1.0:
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
globby@11.1.0: globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'} engines: {node: '>=10'}
globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
google-protobuf@3.21.2: google-protobuf@3.21.2:
resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==}
@ -6702,6 +6754,9 @@ packages:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'} engines: {node: '>=6'}
harfbuzzjs@0.4.7:
resolution: {integrity: sha512-fGrMB7gk+x1ye++cN+OgDnHLLz8wg6aW26VPb8Q14V6XeZKGC0BCALe+ZEnwASU/b+YprBnRTELMJAlwy9jrLw==}
has-bigints@1.0.2: has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
@ -7896,6 +7951,10 @@ packages:
resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==} resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
engines: {node: '>=10'} engines: {node: '>=10'}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
minimatch@9.0.1: minimatch@9.0.1:
resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@ -8083,6 +8142,10 @@ packages:
resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==}
engines: {node: '>=10'} engines: {node: '>=10'}
node-abi@3.75.0:
resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==}
engines: {node: '>=10'}
node-abort-controller@3.1.1: node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@ -9902,6 +9965,9 @@ packages:
through@2.3.8: through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-glob@0.2.9:
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
tiny-invariant@1.3.3: tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@ -10526,6 +10592,10 @@ packages:
resolution: {integrity: sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==} resolution: {integrity: sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==}
engines: {node: '>=12'} engines: {node: '>=12'}
wawoff2@2.0.1:
resolution: {integrity: sha512-r0CEmvpH63r4T15ebFqeOjGqU4+EgTx4I510NtK35EMciSdcTxCw3Byy3JnBonz7iyIFZ0AbVo0bbFpEVuhCYA==}
hasBin: true
web-push@3.6.7: web-push@3.6.7:
resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==} resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
@ -14556,6 +14626,10 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.15.21 '@types/node': 22.15.21
'@types/wawoff2@1.0.2':
dependencies:
'@types/node': 22.15.21
'@types/web-push@3.6.4': '@types/web-push@3.6.4':
dependencies: dependencies:
'@types/node': 22.15.21 '@types/node': 22.15.21
@ -17189,6 +17263,10 @@ snapshots:
dependencies: dependencies:
resolve-pkg-maps: 1.0.0 resolve-pkg-maps: 1.0.0
get-tsconfig@4.10.1:
dependencies:
resolve-pkg-maps: 1.0.0
getos@3.2.1: getos@3.2.1:
dependencies: dependencies:
async: 3.2.4 async: 3.2.4
@ -17240,7 +17318,7 @@ snapshots:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
inflight: 1.0.6 inflight: 1.0.6
inherits: 2.0.4 inherits: 2.0.4
minimatch: 5.1.2 minimatch: 5.1.6
once: 1.4.0 once: 1.4.0
global-dirs@3.0.1: global-dirs@3.0.1:
@ -17257,6 +17335,8 @@ snapshots:
dependencies: dependencies:
define-properties: 1.2.1 define-properties: 1.2.1
globalyzer@0.1.0: {}
globby@11.1.0: globby@11.1.0:
dependencies: dependencies:
array-union: 2.1.0 array-union: 2.1.0
@ -17266,6 +17346,8 @@ snapshots:
merge2: 1.4.1 merge2: 1.4.1
slash: 3.0.0 slash: 3.0.0
globrex@0.1.2: {}
google-protobuf@3.21.2: google-protobuf@3.21.2:
optional: true optional: true
@ -17319,6 +17401,8 @@ snapshots:
hard-rejection@2.1.0: {} hard-rejection@2.1.0: {}
harfbuzzjs@0.4.7: {}
has-bigints@1.0.2: {} has-bigints@1.0.2: {}
has-flag@3.0.0: {} has-flag@3.0.0: {}
@ -18857,6 +18941,10 @@ snapshots:
dependencies: dependencies:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
minimatch@9.0.1: minimatch@9.0.1:
dependencies: dependencies:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
@ -19058,6 +19146,11 @@ snapshots:
dependencies: dependencies:
semver: 7.7.2 semver: 7.7.2
node-abi@3.75.0:
dependencies:
semver: 7.7.2
optional: true
node-abort-controller@3.1.1: {} node-abort-controller@3.1.1: {}
node-addon-api@3.2.1: node-addon-api@3.2.1:
@ -19746,7 +19839,7 @@ snapshots:
minimist: 1.2.8 minimist: 1.2.8
mkdirp-classic: 0.5.3 mkdirp-classic: 0.5.3
napi-build-utils: 2.0.0 napi-build-utils: 2.0.0
node-abi: 3.74.0 node-abi: 3.75.0
pump: 3.0.2 pump: 3.0.2
rc: 1.2.8 rc: 1.2.8
simple-get: 4.0.1 simple-get: 4.0.1
@ -21044,6 +21137,11 @@ snapshots:
through@2.3.8: {} through@2.3.8: {}
tiny-glob@0.2.9:
dependencies:
globalyzer: 0.1.0
globrex: 0.1.2
tiny-invariant@1.3.3: {} tiny-invariant@1.3.3: {}
tinybench@2.9.0: {} tinybench@2.9.0: {}
@ -21171,7 +21269,7 @@ snapshots:
tsx@4.19.4: tsx@4.19.4:
dependencies: dependencies:
esbuild: 0.25.4 esbuild: 0.25.4
get-tsconfig: 4.10.0 get-tsconfig: 4.10.1
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
@ -21638,6 +21736,10 @@ snapshots:
wanakana@5.3.1: {} wanakana@5.3.1: {}
wawoff2@2.0.1:
dependencies:
argparse: 2.0.1
web-push@3.6.7: web-push@3.6.7:
dependencies: dependencies:
asn1.js: 5.4.1 asn1.js: 5.4.1

View File

@ -3,6 +3,7 @@ packages:
- packages/frontend-shared - packages/frontend-shared
- packages/frontend - packages/frontend
- packages/frontend-embed - packages/frontend-embed
- packages/icons-subsetter
- packages/sw - packages/sw
- packages/misskey-js - packages/misskey-js
- packages/misskey-js/generator - packages/misskey-js/generator

View File

@ -62,6 +62,12 @@
'scripts/**/package.json', 'scripts/**/package.json',
], ],
}, },
{
groupName: '[icons-subsetter] Update dependencies',
matchFileNames: [
'packages/icons-subsetter/**/package.json',
],
},
{ {
groupName: '[GitHub Actions] Update dependencies', groupName: '[GitHub Actions] Update dependencies',
matchFileNames: [ matchFileNames: [

View File

@ -33,10 +33,6 @@ async function copyFrontendFonts() {
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true }); await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
} }
async function copyFrontendTablerIcons() {
await fs.cp('./packages/frontend/node_modules/@tabler/icons-webfont/dist', './built/_frontend_dist_/tabler-icons', { dereference: true, recursive: true });
}
async function copyFrontendLocales() { async function copyFrontendLocales() {
generateDTS(); generateDTS();
@ -89,7 +85,6 @@ async function buildBackendStyle() {
async function build() { async function build() {
await Promise.all([ await Promise.all([
copyFrontendFonts(), copyFrontendFonts(),
copyFrontendTablerIcons(),
copyFrontendLocales(), copyFrontendLocales(),
copyBackendViews(), copyBackendViews(),
buildBackendScript(), buildBackendScript(),

View File

@ -10,6 +10,7 @@ const fs = require('fs');
fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/icons-subsetter/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });

View File

@ -32,6 +32,12 @@ await Promise.all([
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}), }),
// icons-subsetterは開発段階では使用されないが、型エラーを抑制するためにはじめの一度だけビルドする
execa('pnpm', ['--filter', 'icons-subsetter', 'build'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
}),
]); ]);
execa('pnpm', ['build-pre', '--watch'], { execa('pnpm', ['build-pre', '--watch'], {