diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml
index bc6be308d1..e40a4557df 100644
--- a/.github/workflows/check-spdx-license-id.yml
+++ b/.github/workflows/check-spdx-license-id.yml
@@ -58,6 +58,7 @@ jobs:
"packages/frontend/test"
"packages/frontend-embed/@types"
"packages/frontend-embed/src"
+ "packages/icons-subsetter/src"
"packages/misskey-bubble-game/src"
"packages/misskey-reversi/src"
"packages/sw/src"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21ce5932b1..f3b6b894ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
- モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます
- 「全て公開(今までの挙動)」「ローカルのコンテンツだけ公開(=サーバー内で受信されたリモートのコンテンツは公開しない)」「何も公開しない」から選択できます
- デフォルト値は「ローカルのコンテンツだけ公開」になっています
+- Enhance: UIのアイコンデータの読み込みを軽量化
### Client
- Feat: ドライブのUIが強化されました
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a3aedfa9eb..8776f8ca24 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.**
- 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は、Misskeyで使用しているオリジナルのフロントエンドルーティングシステムです。
**vue-routerから影響を多大に受けているので、まずはvue-routerについて学ぶことをお勧めします。**
diff --git a/Dockerfile b/Dockerfile
index aafaa9dc6e..77277db8cb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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/package.json", "./packages/frontend/"]
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/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
diff --git a/package.json b/package.json
index abc4bcdaa9..0f050b78fe 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"packages/frontend-shared",
"packages/frontend",
"packages/frontend-embed",
+ "packages/icons-subsetter",
"packages/backend",
"packages/sw",
"packages/misskey-js",
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index 026ecd96de..440aaf860b 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -14,13 +14,13 @@
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.4",
- "@tabler/icons-webfont": "3.33.0",
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4",
"@vue/compiler-sfc": "3.5.14",
"astring": "1.9.0",
"buraha": "0.0.1",
"estree-walker": "3.0.3",
+ "icons-subsetter": "workspace:*",
"frontend-shared": "workspace:*",
"json5": "2.2.3",
"mfm-js": "0.24.0",
@@ -39,6 +39,7 @@
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.1",
+ "@tabler/icons-webfont": "3.33.0",
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.7",
"@types/micromatch": "4.0.9",
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index c1b2b58beb..459b283e23 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -6,7 +6,11 @@
// https://vitejs.dev/config/build-options.html#build-modulepreload
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 { createApp, defineAsyncComponent } from 'vue';
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 2dcda56ceb..c7b32b5f2d 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -26,7 +26,6 @@
"@rollup/pluginutils": "5.1.4",
"@sentry/vue": "9.22.0",
"@syuilo/aiscript": "0.19.0",
- "@tabler/icons-webfont": "3.33.0",
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4",
"@vue/compiler-sfc": "3.5.14",
@@ -48,6 +47,7 @@
"estree-walker": "3.0.3",
"eventemitter3": "5.0.1",
"frontend-shared": "workspace:*",
+ "icons-subsetter": "workspace:*",
"idb-keyval": "6.2.2",
"insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2",
@@ -99,6 +99,7 @@
"@storybook/types": "8.6.14",
"@storybook/vue3": "8.6.14",
"@storybook/vue3-vite": "8.6.14",
+ "@tabler/icons-webfont": "3.33.0",
"@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.7",
diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts
index 3241f2dc92..354fb95544 100644
--- a/packages/frontend/src/_boot_.ts
+++ b/packages/frontend/src/_boot_.ts
@@ -6,7 +6,11 @@
// https://vitejs.dev/config/build-options.html#build-modulepreload
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 { mainBoot } from '@/boot/main-boot.js';
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index 83472eec3d..8ba7520f35 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.uploadFolder }}
@@ -368,16 +368,14 @@ function onContextmenu(ev: MouseEvent) {
border-color: var(--MI_THEME-accent);
background: var(--MI_THEME-accent);
- &::after {
- content: "\ea5e";
- font-family: 'tabler-icons';
+ &::before {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 12px;
- line-height: 22px;
+ line-height: 18px;
}
}
}
diff --git a/packages/icons-subsetter/README.md b/packages/icons-subsetter/README.md
new file mode 100644
index 0000000000..1249d65644
--- /dev/null
+++ b/packages/icons-subsetter/README.md
@@ -0,0 +1,15 @@
+## これは何
+
+フロントエンドの各パッケージで使用されているtabler iconsのclassをスキャンし、使用されているiconのみを抽出するツールです。
+
+なお、サブセット版に無いアイコンが呼び出された場合は本物のtabler icons フォントにフォールバックするようになっています。
+
+このツールは本番ビルド時にのみ使用されます(開発モードでも最初の1回だけビルドが走りますが、これは型エラーを抑制するためにファイルを置いておく用の措置です)
+
+現時点では `src/generator.ts` の `filesToScan` にスキャン対象のファイルが書かれています。もしこれに当てはまらないファイルをサブセットのスキャン対象とする場合はこの部分を適宜修正してください。
+
+## 使い方
+
+```bash
+pnpm build
+```
diff --git a/packages/icons-subsetter/eslint.config.js b/packages/icons-subsetter/eslint.config.js
new file mode 100644
index 0000000000..957100fd8c
--- /dev/null
+++ b/packages/icons-subsetter/eslint.config.js
@@ -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,
+ },
+ },
+ },
+];
diff --git a/packages/icons-subsetter/package.json b/packages/icons-subsetter/package.json
new file mode 100644
index 0000000000..0a28f9e038
--- /dev/null
+++ b/packages/icons-subsetter/package.json
@@ -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"
+ ]
+}
diff --git a/packages/icons-subsetter/src/generator.ts b/packages/icons-subsetter/src/generator.ts
new file mode 100644
index 0000000000..1a9e3d8fd2
--- /dev/null
+++ b/packages/icons-subsetter/src/generator.ts
@@ -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();
+ 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();
+ for (const [key, dir] of Object.entries(filesToScan)) {
+ console.log(`Scanning ${key}...`);
+
+ const iconsToPack = new Set();
+
+ 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);
+});
diff --git a/packages/icons-subsetter/src/subsetter.ts b/packages/icons-subsetter/src/subsetter.ts
new file mode 100644
index 0000000000..cd1aed2890
--- /dev/null
+++ b/packages/icons-subsetter/src/subsetter.ts
@@ -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) {
+ 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();
+
+ 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;
+}
diff --git a/packages/icons-subsetter/tsconfig.json b/packages/icons-subsetter/tsconfig.json
new file mode 100644
index 0000000000..08315a91cf
--- /dev/null
+++ b/packages/icons-subsetter/tsconfig.json
@@ -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": []
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a8f2f4dc1..9cb9caaa94 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -728,9 +728,6 @@ importers:
'@syuilo/aiscript':
specifier: 0.19.0
version: 0.19.0
- '@tabler/icons-webfont':
- specifier: 3.33.0
- version: 3.33.0
'@twemoji/parser':
specifier: 15.1.1
version: 15.1.1
@@ -794,6 +791,9 @@ importers:
frontend-shared:
specifier: workspace:*
version: link:../frontend-shared
+ icons-subsetter:
+ specifier: workspace:*
+ version: link:../icons-subsetter
idb-keyval:
specifier: 6.2.2
version: 6.2.2
@@ -942,6 +942,9 @@ importers:
'@storybook/vue3-vite':
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))
+ '@tabler/icons-webfont':
+ specifier: 3.33.0
+ version: 3.33.0
'@testing-library/vue':
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))
@@ -1086,9 +1089,6 @@ importers:
'@rollup/pluginutils':
specifier: 5.1.4
version: 5.1.4(rollup@4.41.0)
- '@tabler/icons-webfont':
- specifier: 3.33.0
- version: 3.33.0
'@twemoji/parser':
specifier: 15.1.1
version: 15.1.1
@@ -1110,6 +1110,9 @@ importers:
frontend-shared:
specifier: workspace:*
version: link:../frontend-shared
+ icons-subsetter:
+ specifier: workspace:*
+ version: link:../icons-subsetter
json5:
specifier: 2.2.3
version: 2.2.3
@@ -1156,6 +1159,9 @@ importers:
'@misskey-dev/summaly':
specifier: 5.2.1
version: 5.2.1
+ '@tabler/icons-webfont':
+ specifier: 3.33.0
+ version: 3.33.0
'@testing-library/vue':
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))
@@ -1272,6 +1278,40 @@ importers:
specifier: 10.1.3
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:
dependencies:
eventemitter3:
@@ -4487,6 +4527,9 @@ packages:
'@types/vary@1.1.3':
resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==}
+ '@types/wawoff2@1.0.2':
+ resolution: {integrity: sha512-UVq4NxMuBywz5gklafg1HcENrK02trh/q+ifyvO6xktflGXRtf11GYsia3/l5nxg2edFQRlnUT6ivnk898WDiw==}
+
'@types/web-push@3.6.4':
resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==}
@@ -6602,6 +6645,9 @@ packages:
get-tsconfig@4.10.0:
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
+ get-tsconfig@4.10.1:
+ resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
+
getos@3.2.1:
resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==}
@@ -6657,10 +6703,16 @@ packages:
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
engines: {node: '>= 0.4'}
+ globalyzer@0.1.0:
+ resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
+
globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
+ globrex@0.1.2:
+ resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
+
google-protobuf@3.21.2:
resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==}
@@ -6702,6 +6754,9 @@ packages:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
+ harfbuzzjs@0.4.7:
+ resolution: {integrity: sha512-fGrMB7gk+x1ye++cN+OgDnHLLz8wg6aW26VPb8Q14V6XeZKGC0BCALe+ZEnwASU/b+YprBnRTELMJAlwy9jrLw==}
+
has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
@@ -7896,6 +7951,10 @@ packages:
resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
engines: {node: '>=10'}
+ minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
+
minimatch@9.0.1:
resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -8083,6 +8142,10 @@ packages:
resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==}
engines: {node: '>=10'}
+ node-abi@3.75.0:
+ resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==}
+ engines: {node: '>=10'}
+
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@@ -9902,6 +9965,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+ tiny-glob@0.2.9:
+ resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
+
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -10526,6 +10592,10 @@ packages:
resolution: {integrity: sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==}
engines: {node: '>=12'}
+ wawoff2@2.0.1:
+ resolution: {integrity: sha512-r0CEmvpH63r4T15ebFqeOjGqU4+EgTx4I510NtK35EMciSdcTxCw3Byy3JnBonz7iyIFZ0AbVo0bbFpEVuhCYA==}
+ hasBin: true
+
web-push@3.6.7:
resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
engines: {node: '>= 16'}
@@ -14556,6 +14626,10 @@ snapshots:
dependencies:
'@types/node': 22.15.21
+ '@types/wawoff2@1.0.2':
+ dependencies:
+ '@types/node': 22.15.21
+
'@types/web-push@3.6.4':
dependencies:
'@types/node': 22.15.21
@@ -17189,6 +17263,10 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
+ get-tsconfig@4.10.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
getos@3.2.1:
dependencies:
async: 3.2.4
@@ -17240,7 +17318,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 5.1.2
+ minimatch: 5.1.6
once: 1.4.0
global-dirs@3.0.1:
@@ -17257,6 +17335,8 @@ snapshots:
dependencies:
define-properties: 1.2.1
+ globalyzer@0.1.0: {}
+
globby@11.1.0:
dependencies:
array-union: 2.1.0
@@ -17266,6 +17346,8 @@ snapshots:
merge2: 1.4.1
slash: 3.0.0
+ globrex@0.1.2: {}
+
google-protobuf@3.21.2:
optional: true
@@ -17319,6 +17401,8 @@ snapshots:
hard-rejection@2.1.0: {}
+ harfbuzzjs@0.4.7: {}
+
has-bigints@1.0.2: {}
has-flag@3.0.0: {}
@@ -18857,6 +18941,10 @@ snapshots:
dependencies:
brace-expansion: 2.0.1
+ minimatch@5.1.6:
+ dependencies:
+ brace-expansion: 2.0.1
+
minimatch@9.0.1:
dependencies:
brace-expansion: 2.0.1
@@ -19058,6 +19146,11 @@ snapshots:
dependencies:
semver: 7.7.2
+ node-abi@3.75.0:
+ dependencies:
+ semver: 7.7.2
+ optional: true
+
node-abort-controller@3.1.1: {}
node-addon-api@3.2.1:
@@ -19746,7 +19839,7 @@ snapshots:
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 2.0.0
- node-abi: 3.74.0
+ node-abi: 3.75.0
pump: 3.0.2
rc: 1.2.8
simple-get: 4.0.1
@@ -21044,6 +21137,11 @@ snapshots:
through@2.3.8: {}
+ tiny-glob@0.2.9:
+ dependencies:
+ globalyzer: 0.1.0
+ globrex: 0.1.2
+
tiny-invariant@1.3.3: {}
tinybench@2.9.0: {}
@@ -21171,7 +21269,7 @@ snapshots:
tsx@4.19.4:
dependencies:
esbuild: 0.25.4
- get-tsconfig: 4.10.0
+ get-tsconfig: 4.10.1
optionalDependencies:
fsevents: 2.3.3
@@ -21638,6 +21736,10 @@ snapshots:
wanakana@5.3.1: {}
+ wawoff2@2.0.1:
+ dependencies:
+ argparse: 2.0.1
+
web-push@3.6.7:
dependencies:
asn1.js: 5.4.1
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 8d4e0f73eb..9a034e257b 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,6 +3,7 @@ packages:
- packages/frontend-shared
- packages/frontend
- packages/frontend-embed
+ - packages/icons-subsetter
- packages/sw
- packages/misskey-js
- packages/misskey-js/generator
diff --git a/renovate.json5 b/renovate.json5
index 23a26eb712..a57df8befb 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -62,6 +62,12 @@
'scripts/**/package.json',
],
},
+ {
+ groupName: '[icons-subsetter] Update dependencies',
+ matchFileNames: [
+ 'packages/icons-subsetter/**/package.json',
+ ],
+ },
{
groupName: '[GitHub Actions] Update dependencies',
matchFileNames: [
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index 8ab341795c..e610a72380 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -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 });
}
-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() {
generateDTS();
@@ -89,7 +85,6 @@ async function buildBackendStyle() {
async function build() {
await Promise.all([
copyFrontendFonts(),
- copyFrontendTablerIcons(),
copyFrontendLocales(),
copyBackendViews(),
buildBackendScript(),
diff --git a/scripts/clean.js b/scripts/clean.js
index 86c19281ea..69a8df76af 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -10,6 +10,7 @@ const fs = require('fs');
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-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/misskey-js/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index 3f66028bee..e500510b9e 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -32,6 +32,12 @@ await Promise.all([
stdout: process.stdout,
stderr: process.stderr,
}),
+ // icons-subsetterは開発段階では使用されないが、型エラーを抑制するためにはじめの一度だけビルドする
+ execa('pnpm', ['--filter', 'icons-subsetter', 'build'], {
+ cwd: _dirname + '/../',
+ stdout: process.stdout,
+ stderr: process.stderr,
+ }),
]);
execa('pnpm', ['build-pre', '--watch'], {