From 115d91812e4bc25a56126f23b4ad13b07d451552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:30:45 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20shiki=E3=81=AE=E8=A8=80?= =?UTF-8?q?=E8=AA=9E=E3=83=BB=E3=83=86=E3=83=BC=E3=83=9E=E3=81=AE=E5=AE=9A?= =?UTF-8?q?=E7=BE=A9=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92CDN(esm.s?= =?UTF-8?q?h)=E3=81=8B=E3=82=89=E5=8F=96=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B=20(#13598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): shikiの言語・テーマの定義ファイルをCDN(esm.sh)から取るようにする * fix CHANGELOG.md --- CHANGELOG.md | 2 ++ packages/frontend/package.json | 2 +- .../frontend/src/components/MkCode.core.vue | 10 +++---- packages/frontend/src/index.html | 2 +- .../frontend/src/scripts/code-highlighter.ts | 18 ++++++------ packages/frontend/src/scripts/theme.ts | 4 +-- packages/frontend/src/store.ts | 1 - packages/frontend/vite.config.ts | 29 +++++++++++++++++++ pnpm-lock.yaml | 20 ++++++++----- 9 files changed, 61 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d948127d06..18dd07f1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/459) - Fix: ページタイトルでローカルユーザーとリモートユーザーの区別がつかない問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/528) +- Fix: コードブロックのシンタックスハイライトで使用される定義ファイルをCDNから取得するように #13177 + - CDNから取得せずMisskey本体にバンドルする場合は`pacakges/frontend/vite.config.ts`を修正してください。 ### Server - Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 682def8e8d..db7f7b76f6 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -60,7 +60,7 @@ "rollup": "4.12.0", "sanitize-html": "2.12.1", "sass": "1.71.1", - "shiki": "1.1.7", + "shiki": "1.2.0", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.162.0", diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index 872517b6aa..c0e7df5dac 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -9,9 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref, computed, watch } from 'vue'; -import { bundledLanguagesInfo } from 'shiki'; -import type { BuiltinLanguage } from 'shiki'; +import { computed, ref, watch } from 'vue'; +import { bundledLanguagesInfo } from 'shiki/langs'; +import type { BundledLanguage } from 'shiki/langs'; import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js'; import { defaultStore } from '@/store.js'; @@ -23,7 +23,7 @@ const props = defineProps<{ const highlighter = await getHighlighter(); const darkMode = defaultStore.reactiveState.darkMode; -const codeLang = ref<BuiltinLanguage | 'aiscript'>('js'); +const codeLang = ref<BundledLanguage | 'aiscript'>('js'); const [lightThemeName, darkThemeName] = await Promise.all([ getTheme('light', true), @@ -42,7 +42,7 @@ const html = computed(() => highlighter.codeToHtml(props.code, { })); async function fetchLanguage(to: string): Promise<void> { - const language = to as BuiltinLanguage; + const language = to as BundledLanguage; // Check for the loaded languages, and load the language if it's not loaded yet. if (!highlighter.getLoadedLanguages().includes(language)) { diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html index cd84145f40..08ff0c58dd 100644 --- a/packages/frontend/src/index.html +++ b/packages/frontend/src/index.html @@ -18,7 +18,7 @@ http-equiv="Content-Security-Policy" content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/; worker-src 'self'; - script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com; + script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts index 5dd0a3be78..e94027d302 100644 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ b/packages/frontend/src/scripts/code-highlighter.ts @@ -3,18 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { bundledThemesInfo } from 'shiki'; import { getHighlighterCore, loadWasm } from 'shiki/core'; import darkPlus from 'shiki/themes/dark-plus.mjs'; +import { bundledThemesInfo } from 'shiki/themes'; +import { bundledLanguagesInfo } from 'shiki/langs'; import { unique } from './array.js'; import { deepClone } from './clone.js'; import { deepMerge } from './merge.js'; -import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki'; +import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core'; import { ColdDeviceStorage } from '@/store.js'; import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; -let _highlighter: Highlighter | null = null; +let _highlighter: HighlighterCore | null = null; export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>; export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>; @@ -51,16 +52,14 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise return darkPlus; } -export async function getHighlighter(): Promise<Highlighter> { +export async function getHighlighter(): Promise<HighlighterCore> { if (!_highlighter) { return await initHighlighter(); } return _highlighter; } -export async function initHighlighter() { - const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json'); - +async function initHighlighter() { await loadWasm(import('shiki/onig.wasm?init')); // テーマの重複を消す @@ -69,11 +68,12 @@ export async function initHighlighter() { ...(await Promise.all([getTheme('light'), getTheme('dark')])), ]); + const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript'); const highlighter = await getHighlighterCore({ themes, langs: [ - import('shiki/langs/javascript.mjs'), - aiScriptGrammar.default as unknown as LanguageRegistration, + ...(jsLangInfo ? [async () => await jsLangInfo.import()] : []), + async () => (await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json')).default as unknown as LanguageRegistration, ], }); diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index 5f7e88bd9f..c7f8b3d596 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -6,7 +6,7 @@ import { ref } from 'vue'; import tinycolor from 'tinycolor2'; import { deepClone } from './clone.js'; -import type { BuiltinTheme } from 'shiki'; +import type { BundledTheme } from 'shiki/themes'; import { globalEvents } from '@/events.js'; import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; @@ -20,7 +20,7 @@ export type Theme = { base?: 'dark' | 'light'; props: Record<string, string>; codeHighlighter?: { - base: BuiltinTheme; + base: BundledTheme; overrides?: Record<string, any>; } | { base: '_none_'; diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index a335ed33bb..7742acc60d 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -7,7 +7,6 @@ import { markRaw, ref } from 'vue'; import * as Misskey from 'misskey-js'; import { miLocalStorage } from './local-storage.js'; import type { SoundType } from '@/scripts/sound.js'; -import type { BuiltinTheme as ShikiBuiltinTheme } from 'shiki'; import { Storage } from '@/pizzax.js'; import { hemisphere } from '@/scripts/intl-const.js'; diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 35d112f6ec..82eb2af464 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -5,11 +5,30 @@ import { type UserConfig, defineConfig } from 'vite'; import locales from '../../locales/index.js'; import meta from '../../package.json'; +import packageInfo from './package.json' assert { type: 'json' }; import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js'; import pluginJson5 from './vite.json5.js'; const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; +/** + * Misskeyのフロントエンドにバンドルせず、CDNなどから別途読み込むリソースを記述する。 + * CDNを使わずにバンドルしたい場合、以下の配列から該当要素を削除orコメントアウトすればOK + */ +const externalPackages = [ + // shiki(コードブロックのシンタックスハイライトで使用中)はテーマ・言語の定義の容量が大きいため、それらはCDNから読み込む + { + name: 'shiki', + match: /^shiki\/(?<subPkg>(langs|themes))$/, + path(id: string, pattern: RegExp): string { + const match = pattern.exec(id)?.groups; + return match + ? `https://esm.sh/shiki@${packageInfo.dependencies.shiki}/${match['subPkg']}` + : id; + }, + }, +]; + const hash = (str: string, seed = 0): number => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; @@ -112,6 +131,7 @@ export function getConfig(): UserConfig { input: { app: './src/_boot_.ts', }, + external: externalPackages.map(p => p.match), output: { manualChunks: { vue: ['vue'], @@ -119,6 +139,15 @@ export function getConfig(): UserConfig { }, chunkFileNames: process.env.NODE_ENV === 'production' ? '[hash:8].js' : '[name]-[hash:8].js', assetFileNames: process.env.NODE_ENV === 'production' ? '[hash:8][extname]' : '[name]-[hash:8][extname]', + paths(id) { + for (const p of externalPackages) { + if (p.match.test(id)) { + return p.path(id, p.match); + } + } + + return id; + }, }, }, cssCodeSplit: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e29c1162b..2906eef4d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -806,8 +806,8 @@ importers: specifier: 1.71.1 version: 1.71.1 shiki: - specifier: 1.1.7 - version: 1.1.7 + specifier: 1.2.0 + version: 1.2.0 strict-event-emitter-types: specifier: 2.0.0 version: 2.0.0 @@ -5324,8 +5324,8 @@ packages: string-argv: 0.3.1 dev: true - /@shikijs/core@1.1.7: - resolution: {integrity: sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==} + /@shikijs/core@1.2.0: + resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} dev: false /@sideway/address@4.1.4: @@ -6646,7 +6646,7 @@ packages: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.4.21(typescript@5.3.3) - vue-component-type-helpers: 1.8.27 + vue-component-type-helpers: 2.0.6 transitivePeerDependencies: - encoding - supports-color @@ -17658,10 +17658,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /shiki@1.1.7: - resolution: {integrity: sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==} + /shiki@1.2.0: + resolution: {integrity: sha512-xLhiTMOIUXCv5DqJ4I70GgQCtdlzsTqFLZWcMHHG3TAieBUbvEGthdrlPDlX4mL/Wszx9C6rEcxU6kMlg4YlxA==} dependencies: - '@shikijs/core': 1.1.7 + '@shikijs/core': 1.2.0 dev: false /side-channel@1.0.4: @@ -19445,6 +19445,10 @@ packages: resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==} dev: true + /vue-component-type-helpers@2.0.6: + resolution: {integrity: sha512-qdGXCtoBrwqk1BT6r2+1Wcvl583ZVkuSZ3or7Y1O2w5AvWtlvvxwjGhmz5DdPJS9xqRdDlgTJ/38ehWnEi0tFA==} + dev: true + /vue-demi@0.14.7(vue@3.4.21): resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'}