diff --git a/src/bundler.ts b/src/bundler.ts index 1fea26366..56a5ccd4a 100644 --- a/src/bundler.ts +++ b/src/bundler.ts @@ -58,6 +58,7 @@ export async function extendBundler( const dynamicOptions: ResourceDynamicPluginOptions = { prerenderTargs: options.prerenderTargets, + ssr: nuxt.options.ssr, dev: nuxt.options.dev, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client } diff --git a/src/module.ts b/src/module.ts index 4b954a675..903f1812b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -113,7 +113,8 @@ export default defineNuxtModule({ // for privates nuxt.options.runtimeConfig.i18n = defu(nuxt.options.runtimeConfig.i18n, { - precompile: options.precompile + precompile: options.precompile, + ssr: nuxt.options.ssr }) /** diff --git a/src/runtime/server/dynamic.ts b/src/runtime/server/dynamic.ts index be8eb9e8e..38b1c11d5 100644 --- a/src/runtime/server/dynamic.ts +++ b/src/runtime/server/dynamic.ts @@ -1,6 +1,6 @@ import { defineEventHandler, setResponseHeader, createError } from 'h3' // @ts-expect-error -import { useStorage } from '#imports' +import { useStorage, useRuntimeConfig } from '#imports' import { relative, join } from 'pathe' import { isObject, isFunction } from '@intlify/shared' @@ -10,18 +10,32 @@ import type { PrerenderTarget } from '../../utils' type ResourceMapValue = Pick & { locale?: string } export default defineEventHandler(async event => { + const config = useRuntimeConfig() + const hash = event.context.params?.hash if (hash == null) { throw createError({ statusMessage: `require the 'hash'`, statusCode: 400 }) } - const i18nMeta = await getI18nMeta() - const [filename] = hash.split('.') // request from `xxx.js` + /** + * resolve pre-comilable config or locales + */ + + const i18nMeta = await getI18nMeta(config.i18n.ssr) + const filename = getFilename(hash) const target = i18nMeta[filename] - const loadPath = await resolveModule(target.path) + /** + * import a module that can pre-compile config or locales. + */ + + const loadPath = await resolveModule(target.path, config.i18n.ssr) const loader = await import(loadPath).then(m => m.default || m) + /** + * pre-compile + */ + if (target.type === 'locale') { if (target.locale == null) { throw createError({ statusMessage: `not found locale`, statusCode: 500 }) @@ -41,16 +55,26 @@ export default defineEventHandler(async event => { } }) -async function getI18nMeta() { - return (await useStorage().getItem('build:dist:server:i18n-meta.json')) as Record +function getFilename(hash: string) { + const [filename] = hash.split('.') // request from `xxx.js` + return filename +} + +const resourcePlace = (ssr = true) => (ssr ? 'server' : 'client') + +async function getI18nMeta(ssr = true) { + return (await useStorage().getItem(`build:dist:${resourcePlace(ssr)}:i18n-meta.json`)) as Record< + string, + ResourceMapValue + > } -async function resolveModule(path: string) { +async function resolveModule(path: string, ssr = true) { const storage = await useStorage() const rootMount = await storage.getMount('root') const root = rootMount.driver.options.base const rootRelative = relative(new URL(import.meta.url).pathname, root) - return join(rootRelative, 'dist/server', path) + return join(rootRelative, `dist/${resourcePlace(ssr)}`, path) } async function precompileLocale(locale: string, filename: string, messages: LocaleMessages) { diff --git a/src/transform/dynamic.ts b/src/transform/dynamic.ts index 2887b0f26..151fbc6eb 100644 --- a/src/transform/dynamic.ts +++ b/src/transform/dynamic.ts @@ -11,11 +11,12 @@ import type { PrerenderTargets, PrerenderTarget } from '../utils' export interface ResourceDynamicPluginOptions { prerenderTargs: PrerenderTargets + ssr: boolean dev?: boolean sourcemap?: boolean } -type ResourceMapValue = Pick & { ref: string; locale?: string } +type ResourceMapValue = Pick & { ref: string; hash: string; locale?: string } const debug = createDebug('@nuxtjs/i18n:transform:dynamic') @@ -73,10 +74,12 @@ export const ResourceDynamicPlugin = createUnplugin((options: ResourceDynamicPlu const ref = this.emitFile({ // @ts-expect-error type: 'chunk', - id + id, + preserveSignature: 'strict' }) as unknown as string - resoucesMap.set(hash, { + resoucesMap.set(id, { + hash, type: query.locale ? 'locale' : 'config', locale: query.locale as string, ref @@ -87,10 +90,18 @@ export const ResourceDynamicPlugin = createUnplugin((options: ResourceDynamicPlu }, vite: { - generateBundle() { - // console.log('generateBundle: outputOptions', outputOptions) - const resources = [...resoucesMap].reduce((obj, [hash, { type, locale, ref }]) => { - obj[hash] = { type, locale, path: this.getFileName(ref) } + generateBundle(outputOptions) { + /** + * NOTE: + * avoid generating i18n-meta.json for SPA mode, + * because some i18n resources doesn't bundle on server-side + */ + if (!options.ssr && outputOptions.dir?.endsWith('server')) { + return + } + + const resources = [...resoucesMap].reduce((obj, [_, { hash, type, locale, ref }]) => { + obj[hash] = { hash, type, locale, path: this.getFileName(ref) } return obj }, {} as Record & { path: string }>) debug('generateBundle: resources', resources)