forked from mirrors/misskey
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})
This commit is contained in:
51
packages/frontend-embed/build.ts
Normal file
51
packages/frontend-embed/build.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import url from 'node:url';
|
||||
import path from 'node:path';
|
||||
import { execa } from 'execa';
|
||||
import locales from '../../locales/index.js';
|
||||
import { LocaleInliner } from '../frontend-builder/locale-inliner.js'
|
||||
import { createLogger } from '../frontend-builder/logger';
|
||||
|
||||
// requires node 21 or later
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
||||
const outputDir = __dirname + '/../../built/_frontend_embed_vite_';
|
||||
|
||||
/**
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function viteBuild() {
|
||||
await execa('vite', ['build'], {
|
||||
cwd: __dirname,
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function buildAllLocale() {
|
||||
const logger = createLogger()
|
||||
const inliner = await LocaleInliner.create({
|
||||
outputDir,
|
||||
logger,
|
||||
scriptsDir: 'scripts',
|
||||
i18nFile: 'src/i18n.ts',
|
||||
})
|
||||
|
||||
await inliner.loadFiles();
|
||||
|
||||
inliner.collectsModifications();
|
||||
|
||||
await inliner.saveAllLocales(locales);
|
||||
|
||||
if (logger.errorCount > 0) {
|
||||
throw new Error(`Build failed with ${logger.errorCount} errors and ${logger.warningCount} warnings.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function build() {
|
||||
await fs.rm(outputDir, { recursive: true, force: true });
|
||||
await viteBuild();
|
||||
await buildAllLocale();
|
||||
}
|
||||
|
||||
await build();
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"watch": "vite",
|
||||
"build": "vite build",
|
||||
"build": "tsx build.ts",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"eslint": "eslint --quiet \"src/**/*.{ts,vue}\"",
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
@@ -20,8 +20,8 @@
|
||||
"astring": "1.9.0",
|
||||
"buraha": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
"icons-subsetter": "workspace:*",
|
||||
"frontend-shared": "workspace:*",
|
||||
"icons-subsetter": "workspace:*",
|
||||
"json5": "2.2.3",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -63,6 +63,7 @@
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"start-server-and-test": "2.0.12",
|
||||
"tsx": "4.20.3",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.0.5",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
|
||||
@@ -17,15 +17,16 @@ import { createApp, defineAsyncComponent } from 'vue';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-dark.json5';
|
||||
import { MediaProxy } from '@@/js/media-proxy.js';
|
||||
import { storeBootloaderErrors } from '@@/js/store-boot-errors';
|
||||
import { applyTheme, assertIsTheme } from '@/theme.js';
|
||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { serverMetadata } from '@/server-metadata.js';
|
||||
import { url, version, locale, lang, updateLocale } from '@@/js/config.js';
|
||||
import { url, version, lang } from '@@/js/config.js';
|
||||
import { parseEmbedParams } from '@@/js/embed-page.js';
|
||||
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
||||
import { serverContext } from '@/server-context.js';
|
||||
import { i18n, updateI18n } from '@/i18n.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
import type { Theme } from '@/theme.js';
|
||||
|
||||
@@ -76,19 +77,7 @@ if (embedParams.colorMode === 'dark') {
|
||||
//#endregion
|
||||
|
||||
//#region Detect language & fetch translations
|
||||
const localeVersion = localStorage.getItem('localeVersion');
|
||||
const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null);
|
||||
if (localeOutdated) {
|
||||
const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
|
||||
if (res.status === 200) {
|
||||
const newLocale = await res.text();
|
||||
const parsedNewLocale = JSON.parse(newLocale);
|
||||
localStorage.setItem('locale', newLocale);
|
||||
localStorage.setItem('localeVersion', version);
|
||||
updateLocale(parsedNewLocale);
|
||||
updateI18n(parsedNewLocale);
|
||||
}
|
||||
}
|
||||
storeBootloaderErrors({ ...i18n.ts._bootErrors, reload: i18n.ts.reload });
|
||||
//#endregion
|
||||
|
||||
// サイズの制限
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
import { I18n } from '@@/js/i18n.js';
|
||||
import { locale } from '@@/js/locale.js';
|
||||
import type { Locale } from '../../../locales/index.js';
|
||||
import { locale } from '@@/js/config.js';
|
||||
|
||||
export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
|
||||
|
||||
// test 以外では使わないこと。インライン化されてるのでだいたい意味がない
|
||||
export function updateI18n(newLocale: Locale) {
|
||||
i18n.locale = newLocale;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import locales from '../../locales/index.js';
|
||||
import meta from '../../package.json';
|
||||
import packageInfo from './package.json' with { type: 'json' };
|
||||
import pluginJson5 from './vite.json5.js';
|
||||
import pluginRemoveUnrefI18n from '../frontend-builder/rollup-plugin-remove-unref-i18n';
|
||||
|
||||
const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
|
||||
const host = url ? (new URL(url)).hostname : undefined;
|
||||
@@ -85,6 +86,7 @@ export function getConfig(): UserConfig {
|
||||
|
||||
plugins: [
|
||||
pluginVue(),
|
||||
pluginRemoveUnrefI18n(),
|
||||
pluginJson5(),
|
||||
],
|
||||
|
||||
@@ -135,15 +137,20 @@ export function getConfig(): UserConfig {
|
||||
manifest: 'manifest.json',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
app: './src/boot.ts',
|
||||
i18n: './src/i18n.ts',
|
||||
entry: './src/boot.ts',
|
||||
},
|
||||
external: externalPackages.map(p => p.match),
|
||||
preserveEntrySignatures: 'allow-extension',
|
||||
output: {
|
||||
manualChunks: {
|
||||
vue: ['vue'],
|
||||
// dependencies of i18n.ts
|
||||
'config': ['@@/js/config.js'],
|
||||
},
|
||||
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]',
|
||||
entryFileNames: 'scripts/[hash:8].js',
|
||||
chunkFileNames: 'scripts/[hash:8].js',
|
||||
assetFileNames: 'assets/[hash:8][extname]',
|
||||
paths(id) {
|
||||
for (const p of externalPackages) {
|
||||
if (p.match.test(id)) {
|
||||
|
||||
Reference in New Issue
Block a user