From 4db5d44b311621a8ce419441956641f79feba860 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sat, 18 Apr 2026 21:01:19 +0900 Subject: [PATCH 1/4] fix(unplugin-vue-i18n): preserve vite:json ObjectHook shape for Vite 8 compatibility --- package.json | 3 +- packages/unplugin-vue-i18n/package.json | 4 +++ .../unplugin-vue-i18n/src/core/resource.ts | 34 +++++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index a7a9bb5..54eb5de 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ }, "onlyBuiltDependencies": [ "esbuild", - "oxc-resolver" + "oxc-resolver", + "unrs-resolver" ] }, "license": "MIT", diff --git a/packages/unplugin-vue-i18n/package.json b/packages/unplugin-vue-i18n/package.json index f268588..4d13b01 100644 --- a/packages/unplugin-vue-i18n/package.json +++ b/packages/unplugin-vue-i18n/package.json @@ -11,6 +11,7 @@ }, "peerDependencies": { "petite-vue-i18n": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25", "vue-i18n": "*" }, @@ -18,6 +19,9 @@ "petite-vue-i18n": { "optional": true }, + "vite": { + "optional": true + }, "vue-i18n": { "optional": true } diff --git a/packages/unplugin-vue-i18n/src/core/resource.ts b/packages/unplugin-vue-i18n/src/core/resource.ts index d25c1d2..bedd6c8 100644 --- a/packages/unplugin-vue-i18n/src/core/resource.ts +++ b/packages/unplugin-vue-i18n/src/core/resource.ts @@ -193,15 +193,23 @@ export function resourcePlugin( // json transform handling for normal Vite (not rolldown-vite) if (!isRolldownVite) { const jsonPlugin = getVitePlugin(config, 'vite:json') - if (jsonPlugin) { - // saving `vite:json` plugin instance - const orgTransform = - 'handler' in jsonPlugin.transform! - ? jsonPlugin.transform!.handler - : jsonPlugin.transform! + if (jsonPlugin && jsonPlugin.transform) { + /** + * NOTE(kazupon): + * Detect the hook shape BEFORE mutation. Vite 8 changed `transform` + * to an `ObjectHook` (`{ handler, filter, order, ... }`), while + * earlier versions use a plain function. We must preserve the + * ObjectHook shape so that `filter`/`order` are not lost; otherwise + * Vite 8's `vite:json` will run on already-compiled JS output. + * ref: https://github.com/intlify/bundle-tools/issues/553 + * ref: https://github.com/vitejs/vite/pull/19878/files#diff-2cfbd4f4d8c32727cd8e1a561cffbde0b384a3ce0789340440e144f9d64c10f6R1086-R1088 + */ + const transform = jsonPlugin.transform + const isObjectHook = typeof transform !== 'function' && 'handler' in transform + const orgTransform = isObjectHook ? transform.handler : transform // override json transform - async function overrideJson(code: string, id: string) { + async function overrideJson(this: unknown, code: string, id: string) { const filter = await getFilter() if (!/\.json$/.test(id) || filter(id)) { return @@ -225,14 +233,10 @@ export function resourcePlugin( return orgTransform.apply(this, [code, id]) } - /** - * NOTE(kazupon): - * We need to override the transform function of the `vite:json` plugin for `transform` and `transform.handler`. - * ref: https://github.com/vitejs/vite/pull/19878/files#diff-2cfbd4f4d8c32727cd8e1a561cffbde0b384a3ce0789340440e144f9d64c10f6R1086-R1088 - */ - jsonPlugin.transform = overrideJson - if ('handler' in jsonPlugin.transform!) { - jsonPlugin.transform.handler = overrideJson + if (isObjectHook) { + transform.handler = overrideJson as typeof transform.handler + } else { + jsonPlugin.transform = overrideJson as typeof jsonPlugin.transform } } } From 06d3a29ae2edf105819786f228587f6be91e0084 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sat, 18 Apr 2026 21:06:17 +0900 Subject: [PATCH 2/4] chore: fix knip errors --- knip.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/knip.config.ts b/knip.config.ts index 683b931..f06b440 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -5,5 +5,6 @@ export default { 'scripts/playwright.ts' // jiti ], ignore: ['**/fixtures/**', '.unmaintained/**'], - ignoreDependencies: ['ts-loader', 'lint-staged'] + ignoreDependencies: ['ts-loader', 'lint-staged'], + exclude: ['optionalPeerDependencies'] } satisfies KnipConfig From 4c3c2b37946162ec647b53f32dd50a97ec712d90 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sun, 19 Apr 2026 11:44:12 +0900 Subject: [PATCH 3/4] fix(unplugin-vue-i18n): detect vite:json from config.plugins instead of import(vite) --- .../unplugin-vue-i18n/src/core/resource.ts | 88 ++++++++----------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/packages/unplugin-vue-i18n/src/core/resource.ts b/packages/unplugin-vue-i18n/src/core/resource.ts index bedd6c8..563ccc2 100644 --- a/packages/unplugin-vue-i18n/src/core/resource.ts +++ b/packages/unplugin-vue-i18n/src/core/resource.ts @@ -179,65 +179,53 @@ export function resourcePlugin( sourceMap = config.command === 'build' ? !!config.build.sourcemap : false debug(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`) - // Check if we're using rolldown-vite - const isRolldownVite = !!(await getViteModule()).rolldownVersion - debug(`Using ${isRolldownVite ? 'rolldown-vite' : 'vite'} for the build`) - /** * NOTE(kazupon): - * For the native rolldown plugin, we need to change to another solution from the current workaround. - * Currently, the rolldown team and vite team are discussing this issue. - * https://github.com/vitejs/rolldown-vite/issues/120 + * Override `vite:json` plugin transform to prevent it from processing + * JSON files that unplugin-vue-i18n has already compiled. + * + * We detect the builder by checking whether `vite:json` exists in + * `config.plugins` — rolldown-based Vite (v8+) uses + * `builtin:vite-json` instead, so `getVitePlugin` returns null and + * this block is naturally skipped. This is more reliable than + * `import('vite').rolldownVersion` which can resolve to the wrong + * copy in multi-vite setups (e.g. Nuxt 4 + UnoCSS). + * ref: https://github.com/intlify/bundle-tools/issues/553 */ + const jsonPlugin = getVitePlugin(config, 'vite:json') + if (jsonPlugin && jsonPlugin.transform) { + const transform = jsonPlugin.transform + const isObjectHook = typeof transform !== 'function' && 'handler' in transform + const orgTransform = isObjectHook ? transform.handler : transform + + async function overrideJson(this: unknown, code: string, id: string) { + const filter = await getFilter() + if (!/\.json$/.test(id) || filter(id)) { + return + } - // json transform handling for normal Vite (not rolldown-vite) - if (!isRolldownVite) { - const jsonPlugin = getVitePlugin(config, 'vite:json') - if (jsonPlugin && jsonPlugin.transform) { /** * NOTE(kazupon): - * Detect the hook shape BEFORE mutation. Vite 8 changed `transform` - * to an `ObjectHook` (`{ handler, filter, order, ... }`), while - * earlier versions use a plain function. We must preserve the - * ObjectHook shape so that `filter`/`order` are not lost; otherwise - * Vite 8's `vite:json` will run on already-compiled JS output. - * ref: https://github.com/intlify/bundle-tools/issues/553 - * ref: https://github.com/vitejs/vite/pull/19878/files#diff-2cfbd4f4d8c32727cd8e1a561cffbde0b384a3ce0789340440e144f9d64c10f6R1086-R1088 + * `vite:json` plugin will be handled if the query generated from the result of parse SFC + * with `vite:vue` plugin contains json as follows. + * e.g src/components/HelloI18n.vue?vue&type=i18n&index=1&lang.json + * + * To avoid this, return the result that has already been processed (`enforce: 'pre'`) in the wrapped json plugin. */ - const transform = jsonPlugin.transform - const isObjectHook = typeof transform !== 'function' && 'handler' in transform - const orgTransform = isObjectHook ? transform.handler : transform - - // override json transform - async function overrideJson(this: unknown, code: string, id: string) { - const filter = await getFilter() - if (!/\.json$/.test(id) || filter(id)) { - return - } - - /** - * NOTE(kazupon): - * `vite:json` plugin will be handled if the query generated from the result of parse SFC - * with `vite:vue` plugin contains json as follows. - * e.g src/components/HelloI18n.vue?vue&type=i18n&index=1&lang.json - * - * To avoid this, return the result that has already been processed (`enforce: 'pre'`) in the wrapped json plugin. - */ - const { query } = parseVueRequest(id) - if (query.vue) { - return - } - - debug('org json plugin') - // @ts-expect-error - return orgTransform.apply(this, [code, id]) + const { query } = parseVueRequest(id) + if (query.vue) { + return } - if (isObjectHook) { - transform.handler = overrideJson as typeof transform.handler - } else { - jsonPlugin.transform = overrideJson as typeof jsonPlugin.transform - } + debug('org json plugin') + // @ts-expect-error + return orgTransform.apply(this, [code, id]) + } + + if (isObjectHook) { + transform.handler = overrideJson as typeof transform.handler + } else { + jsonPlugin.transform = overrideJson as typeof jsonPlugin.transform } } }, From 0064e1f4ceb76cfd97bf74545fccd40baadc4de8 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sun, 19 Apr 2026 23:45:45 +0900 Subject: [PATCH 4/4] fix --- .../unplugin-vue-i18n/src/core/resource.ts | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/unplugin-vue-i18n/src/core/resource.ts b/packages/unplugin-vue-i18n/src/core/resource.ts index 563ccc2..8343471 100644 --- a/packages/unplugin-vue-i18n/src/core/resource.ts +++ b/packages/unplugin-vue-i18n/src/core/resource.ts @@ -21,10 +21,6 @@ import type { ResolvedOptions } from '../core/options' import type { PluginOptions } from '../types' import type { VueQuery } from '../vue' -type ViteCompaibleModule = { - rolldownVersion?: boolean -} - const INTLIFY_BUNDLE_IMPORT_ID = '@intlify/unplugin-vue-i18n/messages' const VIRTUAL_PREFIX = '\0' const RE_INTLIFY_BUNDLE_IMPORT_ID = new RegExp(`^${INTLIFY_BUNDLE_IMPORT_ID}$`) @@ -59,20 +55,6 @@ export function resourcePlugin( meta: UnpluginContextMeta, collector?: import('./collector').UsedKeysCollector | null ): UnpluginOptions { - let viteModule: ViteCompaibleModule | null = null - async function getViteModule() { - if (viteModule != null) { - return viteModule - } - try { - viteModule = (await import('vite')) as unknown as ViteCompaibleModule - } catch (e) { - error(`vite not found, please install vite.`, (e as Error).message) - throw e - } - return viteModule - } - function resolveIncludeExclude() { const customBlockInclude = meta.framework === 'vite' ? RE_SFC_I18N_CUSTOM_BLOCK : RE_SFC_I18N_WEBPACK_CUSTOM_BLOCK @@ -99,15 +81,12 @@ export function resourcePlugin( if (meta.framework == 'webpack') { debug('Using filter for webpack') _filter = createFilter(...resolveIncludeExclude()) + } else if (hasViteJsonPlugin) { + debug('Using filter for rollup-vite') + _filter = createFilter(...resolveIncludeExcludeForLegacy()) } else { - const viteModule = await getViteModule() - if (viteModule.rolldownVersion) { - debug('Using filter for rolldown-vite') - _filter = createFilter(...resolveIncludeExclude()) - } else { - debug('Using filter for rollup-vite') - _filter = createFilter(...resolveIncludeExcludeForLegacy()) - } + debug('Using filter for rolldown-vite') + _filter = createFilter(...resolveIncludeExclude()) } return _filter @@ -119,6 +98,7 @@ export function resourcePlugin( let isProduction = false let sourceMap = false + let hasViteJsonPlugin = false const vueI18nAliasName = module debug(`vue-i18n alias name: ${vueI18nAliasName}`) @@ -193,6 +173,7 @@ export function resourcePlugin( * ref: https://github.com/intlify/bundle-tools/issues/553 */ const jsonPlugin = getVitePlugin(config, 'vite:json') + hasViteJsonPlugin = !!jsonPlugin if (jsonPlugin && jsonPlugin.transform) { const transform = jsonPlugin.transform const isObjectHook = typeof transform !== 'function' && 'handler' in transform