misskey/packages/frontend/.storybook/preview.ts
anatawa12 8598f3912e
per-locale bundle & inline locale (#16369)
* feat: split entry file by locale name

* chore: とりあえず transform hook で雑に分割

* chore: とりあえず transform 結果をいい感じに

* chore: concurrent buildで高速化

* chore: vite ではローケルのないものをビルドして後処理でどうにかするように

* chore: 後処理のためにi18n.jを単体になるように切り出す

* chore: use typescript

* chore: remove unref(i18n) in vite build process

* chore: inline variable

* fix: build error

* fix: i18n.ts.something.replaceAll() become error

* chore: ignore export specifier from error

* chore: support i18n.tsx as object

* chore: process literal for all files

* chore: split config and locale

* chore: inline locale name

* chore: remove updating locale in boot common

* chore: use top-level await to load locales

* chore: inline locale

* chore: remove loading locale from boot.js

* chore: remove loading locale from boot.js

* コメント追加

* fix test; fetchに失敗する

* import削除ログをdebugレベルに

* fix: watch pug

* chore: use hash for entry files

* chore: remove es-module-lexer from dependencies

* chore: move to frontend-builder

* chore: use inline locale in embed

* chore: refetch json on hot reload

* feat: store localization related to boot.js in backend in bootloaderLocales localstorage

* 応急処置を戻す

* fix spex

* fix `Using i18n identifier "e" directly. Skipping inlining.` warning

* refactor: use scriptsDir parameter

* chore: remove i18n from depmap

* chore: make build crash if errors

* error -> warn few conditions

* use inline object

* update localstorage keys

* remove accessing locale localstorage

* fix: failed to process i18n.tsx.aaa({x:i18n.bbb})
2025-08-08 11:26:18 +09:00

148 lines
3.9 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { FORCE_RE_RENDER, FORCE_REMOUNT } from '@storybook/core-events';
import { addons } from '@storybook/preview-api';
import { type Preview, setup } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { userDetailed } from './fakes.js';
import { commonHandlers, onUnhandledRequest } from './mocks.js';
import themes from './themes.js';
import '../src/style.scss';
const appInitialized = Symbol();
let lastStory: string | null = null;
let moduleInitialized = false;
let unobserve = () => {};
let misskeyOS = null;
function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
unobserve();
const theme = themes[window.document.documentElement.dataset.misskeyTheme];
if (theme) {
applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]);
} else {
applyTheme(themes['l-light']);
}
const observer = new MutationObserver((entries) => {
for (const entry of entries) {
if (entry.attributeName === 'data-misskey-theme') {
const target = entry.target as HTMLElement;
const theme = themes[target.dataset.misskeyTheme];
if (theme) {
applyTheme(themes[target.dataset.misskeyTheme]);
} else {
target.removeAttribute('style');
}
}
}
});
observer.observe(window.document.documentElement, {
attributes: true,
attributeFilter: ['data-misskey-theme'],
});
unobserve = () => observer.disconnect();
}
function initLocalStorage() {
localStorage.clear();
localStorage.setItem('account', JSON.stringify({
...userDetailed(),
policies: {},
}));
}
initialize({
onUnhandledRequest,
});
initLocalStorage();
queueMicrotask(() => {
Promise.all([
import('../src/components/index.js'),
import('../src/directives/index.js'),
import('../src/widgets/index.js'),
import('../src/theme.js'),
import('../src/preferences.js'),
import('../src/os.js'),
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
setup((app) => {
moduleInitialized = true;
if (app[appInitialized]) {
return;
}
app[appInitialized] = true;
loadTheme(applyTheme);
components(app);
directives(app);
widgets(app);
misskeyOS = os;
if (isChromatic()) {
prefer.commit('animation', false);
}
});
});
});
const preview = {
decorators: [
(Story, context) => {
if (lastStory === context.id) {
lastStory = null;
} else {
lastStory = context.id;
const channel = addons.getChannel();
const resetIndexedDBPromise = globalThis.indexedDB?.databases
? indexedDB.databases().then((r) => {
for (var i = 0; i < r.length; i++) {
indexedDB.deleteDatabase(r[i].name!);
}
}).catch(() => {})
: Promise.resolve();
const resetDefaultStorePromise = import('../src/store').then(({ store }) => {
// @ts-expect-error
store.init();
}).catch(() => {});
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
initLocalStorage();
channel.emit(FORCE_RE_RENDER, { storyId: context.id });
});
}
const story = Story();
if (!moduleInitialized) {
const channel = addons.getChannel();
(globalThis.requestIdleCallback || setTimeout)(() => {
channel.emit(FORCE_REMOUNT, { storyId: context.id });
});
}
return story;
},
(Story, context) => {
return {
setup() {
return {
context,
popups: misskeyOS.popups,
};
},
template:
'<component :is="popup.component" v-for="popup in popups" :key="popup.id" v-bind="popup.props" v-on="popup.events"/>' +
'<story />',
};
},
],
loaders: [mswLoader],
parameters: {
controls: {
exclude: /^__/,
},
msw: {
handlers: commonHandlers,
},
},
} satisfies Preview;
export default preview;