perf: move the 19 non-zh/en dictionaries into a lazy locale chunk
The core client bundle statically imported every third-language dictionary (ja/de/fr/…/zh-HK/MO/TW, ~640KB of source — roughly half the bundle's weight) for a code path most sessions never enter: the sidebar's own t() only consults zh/en plus the better-locale override store, and the dicts' ONLY consumer is that store's register() call. They now ride the existing lazy-chunk machinery as lib/client-locale.js (fetched from /sidebar/bundle only when better-locale is actually installed; a generation counter keeps the async registration moot when the integration effect re-runs or disposes mid-flight). Until the chunk lands, t() keeps the zh/en chain; the store's own revision bump on register re-renders the chrome once the dicts arrive. Core client.js: 1.46MB → 822KB (-44%). The chunk-boundary key-set check moves to the existing runtime test (every shipped dict stays key-set-equal to zh).
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts'
|
||||
|
||||
/** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
|
||||
export const CHUNK_NAMES = ['terminal', 'editor', 'mermaid'] as const
|
||||
export const CHUNK_NAMES = ['terminal', 'editor', 'mermaid', 'locale'] as const
|
||||
export type ChunkName = (typeof CHUNK_NAMES)[number]
|
||||
|
||||
/** Directory of this host-half module (lib/ — the chunk scripts live next to it). */
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
* client.js); an edit that does land while a core HMR happens is caught by
|
||||
* the ETag comparison on the next activation.
|
||||
*/
|
||||
export type ChunkName = 'terminal' | 'editor' | 'mermaid'
|
||||
export type ChunkName = 'terminal' | 'editor' | 'mermaid' | 'locale'
|
||||
|
||||
/** The module exports a chunk factory provides (namespace-ish record). */
|
||||
export type ChunkExports = Record<string, unknown>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Lazy chunk entry: the 19 non-zh/en dictionaries (ja/de/fr/…/zh-HK/MO/TW —
|
||||
* ~640KB of source). The sidebar's own `t()` only ever consults zh/en plus
|
||||
* the better-locale override store, so these dicts are needed ONLY when
|
||||
* @huanlin/dsh-plugin-better-locale is installed: they register into its
|
||||
* override store from there (see the client apply's better-locale
|
||||
* integration). Built as `lib/client-locale.js` and fetched from the
|
||||
* plugin's /sidebar/bundle route on first need — never import this module
|
||||
* from the core bundle: it would drag every language back into the startup
|
||||
* path (see docs/plans/2026-08-31-perf-optimization.md).
|
||||
*/
|
||||
import { ja } from '../locales-ja.ts'
|
||||
import { de } from '../locales-de.ts'
|
||||
import { fr } from '../locales-fr.ts'
|
||||
import { pt } from '../locales-pt.ts'
|
||||
import { ko } from '../locales-ko.ts'
|
||||
import { ar } from '../locales-ar.ts'
|
||||
import { hi } from '../locales-hi.ts'
|
||||
import { id } from '../locales-id.ts'
|
||||
import { tr } from '../locales-tr.ts'
|
||||
import { vi } from '../locales-vi.ts'
|
||||
import { th } from '../locales-th.ts'
|
||||
import { ru } from '../locales-ru.ts'
|
||||
import { it } from '../locales-it.ts'
|
||||
import { nl } from '../locales-nl.ts'
|
||||
import { sv } from '../locales-sv.ts'
|
||||
import { pl } from '../locales-pl.ts'
|
||||
import { zhHK } from '../locales-zh-HK.ts'
|
||||
import { zhTW } from '../locales-zh-TW.ts'
|
||||
import { zhMO } from '../locales-zh-MO.ts'
|
||||
import type { CopyKey } from '../locales.ts'
|
||||
|
||||
/** Key-set check against zh (type-only import — erased before bundling, so
|
||||
* this adds no runtime tie to the core bundle): a dictionary missing or
|
||||
* adding a key fails the build instead of silently falling back to en. */
|
||||
const checked = (dict: Record<CopyKey, string>): Record<string, string> => dict
|
||||
|
||||
/** Every non-zh/en dictionary keyed by its override id, ready for the
|
||||
* better-locale store's `register(ns, dicts)`. */
|
||||
export const localeDicts: Record<string, Record<string, string>> = {
|
||||
ja: checked(ja),
|
||||
de: checked(de),
|
||||
fr: checked(fr),
|
||||
pt: checked(pt),
|
||||
ko: checked(ko),
|
||||
ar: checked(ar),
|
||||
hi: checked(hi),
|
||||
id: checked(id),
|
||||
tr: checked(tr),
|
||||
vi: checked(vi),
|
||||
th: checked(th),
|
||||
ru: checked(ru),
|
||||
it: checked(it),
|
||||
nl: checked(nl),
|
||||
sv: checked(sv),
|
||||
pl: checked(pl),
|
||||
'zh-HK': checked(zhHK),
|
||||
'zh-TW': checked(zhTW),
|
||||
'zh-MO': checked(zhMO),
|
||||
}
|
||||
+19
-8
@@ -24,10 +24,8 @@ import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
||||
import { loadBootDecision } from './prefs.ts'
|
||||
import { SideCardSection } from './SideCardSection.tsx'
|
||||
import { api } from './api.ts'
|
||||
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en,
|
||||
ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
|
||||
zhHK, zhTW, zhMO,
|
||||
} from './locales.ts'
|
||||
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en } from './locales.ts'
|
||||
import { loadChunk } from './chunk-loader.ts'
|
||||
import css from './sidebar.module.css'
|
||||
import './layout.css'
|
||||
|
||||
@@ -82,7 +80,12 @@ export function apply(ctx: Context): void {
|
||||
// the ja dict once the store becomes available.
|
||||
ctx.effect(() => {
|
||||
let dispose: (() => void) | undefined
|
||||
// Guards the async chunk registration below: a sync() re-run (or fiber
|
||||
// disposal) that lands while the chunk is still in flight must render
|
||||
// that registration moot.
|
||||
let generation = 0
|
||||
const sync = (): void => {
|
||||
generation += 1
|
||||
dispose?.()
|
||||
dispose = undefined
|
||||
const store = ctx.get('betterLocale') as
|
||||
@@ -96,10 +99,17 @@ export function apply(ctx: Context): void {
|
||||
| undefined
|
||||
attachBetterLocale(store)
|
||||
if (store !== undefined) {
|
||||
dispose = store.register(LOCALE_NS, {
|
||||
ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
|
||||
'zh-HK': zhHK, 'zh-TW': zhTW, 'zh-MO': zhMO,
|
||||
})
|
||||
// The 19 override dictionaries ride the lazy `locale` chunk: until
|
||||
// it lands, the store has no betterSidebar entries and t() keeps
|
||||
// the zh/en chain; the store's own revision bump on register
|
||||
// re-renders the chrome once the dicts arrive.
|
||||
const myGeneration = generation
|
||||
void loadChunk('locale')
|
||||
.then(mod => {
|
||||
if (myGeneration !== generation) return
|
||||
dispose = store.register(LOCALE_NS, mod.localeDicts as Record<string, Record<string, string>>)
|
||||
})
|
||||
.catch(() => { /* the dicts stay unregistered; the zh/en chain runs */ })
|
||||
}
|
||||
}
|
||||
// Initial check (picks up the store if better-locale activated first).
|
||||
@@ -108,6 +118,7 @@ export function apply(ctx: Context): void {
|
||||
// activates with a persisted override, and when the user switches).
|
||||
const unsubscribe = ctx.locale.subscribe(sync)
|
||||
return () => {
|
||||
generation += 1
|
||||
unsubscribe()
|
||||
dispose?.()
|
||||
attachBetterLocale(undefined)
|
||||
|
||||
+7
-43
@@ -845,49 +845,13 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
*/
|
||||
export const LOCALE_NS = 'betterSidebar'
|
||||
|
||||
// The ja dictionary lives in a sibling file (326 keys) so this module
|
||||
// stays readable. Type-checked against the zh key set: a missing or extra
|
||||
// ja key is a compile error.
|
||||
import { ja as jaDict } from './locales-ja.ts'
|
||||
import { de as deDict } from './locales-de.ts'
|
||||
import { fr as frDict } from './locales-fr.ts'
|
||||
import { pt as ptDict } from './locales-pt.ts'
|
||||
import { ko as koDict } from './locales-ko.ts'
|
||||
import { ar as arDict } from './locales-ar.ts'
|
||||
import { hi as hiDict } from './locales-hi.ts'
|
||||
import { id as idDict } from './locales-id.ts'
|
||||
import { tr as trDict } from './locales-tr.ts'
|
||||
import { vi as viDict } from './locales-vi.ts'
|
||||
import { th as thDict } from './locales-th.ts'
|
||||
import { ru as ruDict } from './locales-ru.ts'
|
||||
import { it as itDict } from './locales-it.ts'
|
||||
import { nl as nlDict } from './locales-nl.ts'
|
||||
import { sv as svDict } from './locales-sv.ts'
|
||||
import { pl as plDict } from './locales-pl.ts'
|
||||
import { zhHK as zhHKDict } from './locales-zh-HK.ts'
|
||||
import { zhTW as zhTWDict } from './locales-zh-TW.ts'
|
||||
import { zhMO as zhMODict } from './locales-zh-MO.ts'
|
||||
|
||||
/** The ja dictionary (key-set-equal to zh, enforced by the type annotation). */
|
||||
export const ja: Record<keyof typeof zh, string> = jaDict as Record<keyof typeof zh, string>
|
||||
export const de: Record<keyof typeof zh, string> = deDict as Record<keyof typeof zh, string>
|
||||
export const fr: Record<keyof typeof zh, string> = frDict as Record<keyof typeof zh, string>
|
||||
export const pt: Record<keyof typeof zh, string> = ptDict as Record<keyof typeof zh, string>
|
||||
export const ko: Record<keyof typeof zh, string> = koDict as Record<keyof typeof zh, string>
|
||||
export const ar: Record<keyof typeof zh, string> = arDict as Record<keyof typeof zh, string>
|
||||
export const hi: Record<keyof typeof zh, string> = hiDict as Record<keyof typeof zh, string>
|
||||
export const id: Record<keyof typeof zh, string> = idDict as Record<keyof typeof zh, string>
|
||||
export const tr: Record<keyof typeof zh, string> = trDict as Record<keyof typeof zh, string>
|
||||
export const vi: Record<keyof typeof zh, string> = viDict as Record<keyof typeof zh, string>
|
||||
export const th: Record<keyof typeof zh, string> = thDict as Record<keyof typeof zh, string>
|
||||
export const ru: Record<keyof typeof zh, string> = ruDict as Record<keyof typeof zh, string>
|
||||
export const it: Record<keyof typeof zh, string> = itDict as Record<keyof typeof zh, string>
|
||||
export const nl: Record<keyof typeof zh, string> = nlDict as Record<keyof typeof zh, string>
|
||||
export const sv: Record<keyof typeof zh, string> = svDict as Record<keyof typeof zh, string>
|
||||
export const pl: Record<keyof typeof zh, string> = plDict as Record<keyof typeof zh, string>
|
||||
export const zhHK: Record<keyof typeof zh, string> = zhHKDict as Record<keyof typeof zh, string>
|
||||
export const zhTW: Record<keyof typeof zh, string> = zhTWDict as Record<keyof typeof zh, string>
|
||||
export const zhMO: Record<keyof typeof zh, string> = zhMODict as Record<keyof typeof zh, string>
|
||||
// The 19 non-zh/en dictionaries live in the LAZY `locale` chunk
|
||||
// (src/client/chunks/locale.tsx → lib/client-locale.js): `t()` only ever
|
||||
// consults zh/en plus the better-locale override store, so those dicts are
|
||||
// needed only when better-locale is installed — the client apply fetches the
|
||||
// chunk there and registers its `localeDicts` into the override store. They
|
||||
// used to sit statically in the core bundle (≈ half its weight) for a code
|
||||
// path most sessions never enter.
|
||||
|
||||
/** The DSH locale service attached by the client apply (absent → browser detection). */
|
||||
let localeService: { getSnapshot(): { active: string } } | undefined
|
||||
|
||||
@@ -145,6 +145,6 @@ describe('/sidebar/bundle route', () => {
|
||||
})
|
||||
|
||||
it('exports the chunk allowlist (mirror of src/client/chunk-loader.ts)', () => {
|
||||
expect([...CHUNK_NAMES]).toEqual(['terminal', 'editor', 'mermaid'])
|
||||
expect([...CHUNK_NAMES]).toEqual(['terminal', 'editor', 'mermaid', 'locale'])
|
||||
})
|
||||
})
|
||||
|
||||
+10
-14
@@ -6,7 +6,8 @@
|
||||
* interpolation.
|
||||
*/
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { LOCALE_NS, attachBetterLocale, attachLocale, en, isZh, ja, relativeTime, t, zh, de, fr, pt, ko, ar, hi, id as idDict, tr, vi, th, ru, it as itDict, nl, sv, pl, zhHK, zhTW, zhMO } from '../src/client/locales.ts'
|
||||
import { LOCALE_NS, attachBetterLocale, attachLocale, en, isZh, relativeTime, t, zh } from '../src/client/locales.ts'
|
||||
import { localeDicts } from '../src/client/chunks/locale.tsx'
|
||||
|
||||
/** Minimal structural fake of the DSH LocaleService face the sidebar uses. */
|
||||
class FakeLocale {
|
||||
@@ -108,20 +109,15 @@ describe('locales (DSH i18n following)', () => {
|
||||
expect(Object.keys(en).sort()).toEqual(Object.keys(zh).sort())
|
||||
})
|
||||
|
||||
it('keeps the ja dictionary key-set-equal to zh', () => {
|
||||
expect(Object.keys(ja).sort()).toEqual(Object.keys(zh).sort())
|
||||
})
|
||||
|
||||
it('keeps every shipped third-language dictionary key-set-equal to zh', () => {
|
||||
// The third-language dicts are typed `Record<keyof typeof zh, string>` via
|
||||
// an `as` cast (unchecked by tsc). This test catches missing/extra keys
|
||||
// introduced when a zh key is added without updating every language.
|
||||
const thirdLangs = {
|
||||
de, fr, pt, ko, ar, hi, idDict, tr, vi, th, ru, itDict, nl, sv, pl, zhHK, zhTW, zhMO,
|
||||
}
|
||||
for (const [lang, dict] of Object.entries(thirdLangs)) {
|
||||
expect(Object.keys(dict as Record<string, string>).sort(), lang).toEqual(Object.keys(zh).sort())
|
||||
// The third-language dicts live in the lazy locale chunk typed as plain
|
||||
// records (the chunk boundary has no key-set type tie to zh). This test
|
||||
// catches missing/extra keys introduced when a zh key is added without
|
||||
// updating every language — a missing key silently falls back to en.
|
||||
for (const [lang, dict] of Object.entries(localeDicts)) {
|
||||
expect(Object.keys(dict).sort(), lang).toEqual(Object.keys(zh).sort())
|
||||
}
|
||||
expect(Object.keys(localeDicts), 'every shipped third language rides the locale chunk').toContain('ja')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -247,7 +243,7 @@ describe('locales (better-locale override)', () => {
|
||||
locale.switchTo('en')
|
||||
attachLocale(locale)
|
||||
|
||||
const betterLocale = new FakeBetterLocale({ [LOCALE_NS]: { ja } })
|
||||
const betterLocale = new FakeBetterLocale({ [LOCALE_NS]: { ja: localeDicts.ja! } })
|
||||
betterLocale.active = 'ja'
|
||||
attachBetterLocale(betterLocale)
|
||||
|
||||
|
||||
+1
-1
@@ -324,7 +324,7 @@ function makeCssPlugin(pluginId: string): BuildPlugin {
|
||||
}
|
||||
|
||||
/** The lazy chunk names (keep in sync with src/bundle-route.ts CHUNK_NAMES). */
|
||||
const CHUNKS = ['terminal', 'editor', 'mermaid']
|
||||
const CHUNKS = ['terminal', 'editor', 'mermaid', 'locale']
|
||||
|
||||
export default [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user