@intlify/unplugin-vue-i18n 11.0.3 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/lib/index.cjs +356 -63
- package/lib/index.d.cts +1 -1
- package/lib/index.d.mts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.mjs +356 -63
- package/lib/types.d.cts +26 -1
- package/lib/types.d.mts +26 -1
- package/lib/types.d.ts +26 -1
- package/lib/vite.cjs +0 -1
- package/lib/vite.mjs +0 -1
- package/lib/webpack.cjs +0 -1
- package/lib/webpack.mjs +0 -1
- package/package.json +2 -2
package/lib/index.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import pc from 'picocolors';
|
|
|
12
12
|
import eslintUitls from '@eslint-community/eslint-utils';
|
|
13
13
|
import { transformVTDirective } from '@intlify/vue-i18n-extensions';
|
|
14
14
|
import { analyze } from '@typescript-eslint/scope-manager';
|
|
15
|
-
import { parse as parse$2, simpleTraverse, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
|
|
16
15
|
|
|
17
16
|
function normalizeGlobOption(val) {
|
|
18
17
|
if (!val) return void 0;
|
|
@@ -38,6 +37,11 @@ function resolveOptions(options) {
|
|
|
38
37
|
const optimizeTranslationDirective = isString(options.optimizeTranslationDirective) || isArray(options.optimizeTranslationDirective) ? options.optimizeTranslationDirective : !!options.optimizeTranslationDirective;
|
|
39
38
|
const translationIdentifiers = /* @__PURE__ */ new Map();
|
|
40
39
|
const transformI18nBlock = typeof options.transformI18nBlock === "function" ? options.transformI18nBlock : null;
|
|
40
|
+
const treeShaking = options.treeShaking ? typeof options.treeShaking === "object" ? {
|
|
41
|
+
safelist: options.treeShaking.safelist || [],
|
|
42
|
+
dynamicKeyStrategy: options.treeShaking.dynamicKeyStrategy || "keep-all",
|
|
43
|
+
scanPatterns: options.treeShaking.scanPatterns
|
|
44
|
+
} : { safelist: [], dynamicKeyStrategy: "keep-all" } : null;
|
|
41
45
|
return {
|
|
42
46
|
include: normalizeGlobOption(options.include || []),
|
|
43
47
|
exclude: normalizeGlobOption(options.exclude),
|
|
@@ -56,7 +60,8 @@ function resolveOptions(options) {
|
|
|
56
60
|
escapeHtml,
|
|
57
61
|
optimizeTranslationDirective,
|
|
58
62
|
translationIdentifiers,
|
|
59
|
-
transformI18nBlock
|
|
63
|
+
transformI18nBlock,
|
|
64
|
+
treeShaking
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -168,7 +173,7 @@ const RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID = new RegExp(
|
|
|
168
173
|
const RE_RESOURCE_FORMAT = /\.(json5?|ya?ml|[c|m]?[j|t]s)$/;
|
|
169
174
|
const RE_SFC_I18N_CUSTOM_BLOCK = /\?vue&type=i18n/;
|
|
170
175
|
const RE_SFC_I18N_WEBPACK_CUSTOM_BLOCK = /blockType=i18n/;
|
|
171
|
-
const debug$
|
|
176
|
+
const debug$3 = createDebug(resolveNamespace("resource"));
|
|
172
177
|
function resourcePlugin({
|
|
173
178
|
onlyLocales,
|
|
174
179
|
include,
|
|
@@ -186,7 +191,7 @@ function resourcePlugin({
|
|
|
186
191
|
allowDynamic,
|
|
187
192
|
escapeHtml,
|
|
188
193
|
transformI18nBlock
|
|
189
|
-
}, meta) {
|
|
194
|
+
}, meta, collector) {
|
|
190
195
|
let viteModule = null;
|
|
191
196
|
async function getViteModule() {
|
|
192
197
|
if (viteModule != null) {
|
|
@@ -214,15 +219,15 @@ function resourcePlugin({
|
|
|
214
219
|
return _filter;
|
|
215
220
|
}
|
|
216
221
|
if (meta.framework == "webpack") {
|
|
217
|
-
debug$
|
|
222
|
+
debug$3("Using filter for webpack");
|
|
218
223
|
_filter = createFilter(...resolveIncludeExclude());
|
|
219
224
|
} else {
|
|
220
225
|
const viteModule2 = await getViteModule();
|
|
221
226
|
if (viteModule2.rolldownVersion) {
|
|
222
|
-
debug$
|
|
227
|
+
debug$3("Using filter for rolldown-vite");
|
|
223
228
|
_filter = createFilter(...resolveIncludeExclude());
|
|
224
229
|
} else {
|
|
225
|
-
debug$
|
|
230
|
+
debug$3("Using filter for rollup-vite");
|
|
226
231
|
_filter = createFilter(...resolveIncludeExcludeForLegacy());
|
|
227
232
|
}
|
|
228
233
|
}
|
|
@@ -234,7 +239,7 @@ function resourcePlugin({
|
|
|
234
239
|
let isProduction = false;
|
|
235
240
|
let sourceMap = false;
|
|
236
241
|
const vueI18nAliasName = module;
|
|
237
|
-
debug$
|
|
242
|
+
debug$3(`vue-i18n alias name: ${vueI18nAliasName}`);
|
|
238
243
|
let vuePlugin = null;
|
|
239
244
|
const getSfcParser = () => {
|
|
240
245
|
return vuePlugin ? getVueCompiler(vuePlugin).parse : parse$1;
|
|
@@ -260,7 +265,7 @@ function resourcePlugin({
|
|
|
260
265
|
__VUE_I18N_PROD_DEVTOOLS__: false
|
|
261
266
|
}
|
|
262
267
|
};
|
|
263
|
-
debug$
|
|
268
|
+
debug$3("define Config:", defineConfig);
|
|
264
269
|
const aliasConfig = {
|
|
265
270
|
resolve: {
|
|
266
271
|
alias: {
|
|
@@ -271,7 +276,7 @@ function resourcePlugin({
|
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
278
|
};
|
|
274
|
-
debug$
|
|
279
|
+
debug$3("alias Config:", aliasConfig);
|
|
275
280
|
return assign(defineConfig, aliasConfig);
|
|
276
281
|
},
|
|
277
282
|
async configResolved(config) {
|
|
@@ -281,9 +286,9 @@ function resourcePlugin({
|
|
|
281
286
|
}
|
|
282
287
|
isProduction = config.isProduction;
|
|
283
288
|
sourceMap = config.command === "build" ? !!config.build.sourcemap : false;
|
|
284
|
-
debug$
|
|
289
|
+
debug$3(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
|
|
285
290
|
const isRolldownVite = !!(await getViteModule()).rolldownVersion;
|
|
286
|
-
debug$
|
|
291
|
+
debug$3(`Using ${isRolldownVite ? "rolldown-vite" : "vite"} for the build`);
|
|
287
292
|
if (!isRolldownVite) {
|
|
288
293
|
const jsonPlugin = getVitePlugin(config, "vite:json");
|
|
289
294
|
if (jsonPlugin) {
|
|
@@ -297,7 +302,7 @@ function resourcePlugin({
|
|
|
297
302
|
if (query.vue) {
|
|
298
303
|
return;
|
|
299
304
|
}
|
|
300
|
-
debug$
|
|
305
|
+
debug$3("org json plugin");
|
|
301
306
|
return orgTransform.apply(this, [code, id]);
|
|
302
307
|
}
|
|
303
308
|
jsonPlugin.transform = overrideJson;
|
|
@@ -322,7 +327,7 @@ function resourcePlugin({
|
|
|
322
327
|
webpack(compiler) {
|
|
323
328
|
isProduction = compiler.options.mode !== "development";
|
|
324
329
|
sourceMap = !!compiler.options.devtool;
|
|
325
|
-
debug$
|
|
330
|
+
debug$3(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
|
|
326
331
|
compiler.options.resolve = normalizeConfigResolveAlias(
|
|
327
332
|
compiler.options.resolve,
|
|
328
333
|
meta.framework
|
|
@@ -331,7 +336,7 @@ function resourcePlugin({
|
|
|
331
336
|
ssr: ssrBuild,
|
|
332
337
|
runtimeOnly
|
|
333
338
|
});
|
|
334
|
-
debug$
|
|
339
|
+
debug$3(
|
|
335
340
|
`set ${vueI18nAliasName}: ${getVueI18nAliasPath({
|
|
336
341
|
ssr: ssrBuild,
|
|
337
342
|
runtimeOnly
|
|
@@ -344,15 +349,15 @@ function resourcePlugin({
|
|
|
344
349
|
__INTLIFY_PROD_DEVTOOLS__: "false"
|
|
345
350
|
})
|
|
346
351
|
);
|
|
347
|
-
debug$
|
|
348
|
-
debug$
|
|
352
|
+
debug$3(`set __VUE_I18N_LEGACY_API__ is '${compositionOnly}'`);
|
|
353
|
+
debug$3(`set __VUE_I18N_FULL_INSTALL__ is '${fullInstall}'`);
|
|
349
354
|
const filter = createFilter(...resolveIncludeExclude());
|
|
350
355
|
if (compiler.options.module) {
|
|
351
356
|
compiler.options.module.rules.push({
|
|
352
357
|
test: RE_RESOURCE_FORMAT,
|
|
353
358
|
type: "javascript/auto",
|
|
354
359
|
include(resource) {
|
|
355
|
-
debug$
|
|
360
|
+
debug$3("webpack resource include", resource);
|
|
356
361
|
return filter(resource);
|
|
357
362
|
}
|
|
358
363
|
});
|
|
@@ -371,7 +376,7 @@ function resourcePlugin({
|
|
|
371
376
|
id: RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID
|
|
372
377
|
},
|
|
373
378
|
async handler(id) {
|
|
374
|
-
debug$
|
|
379
|
+
debug$3("load", id);
|
|
375
380
|
if (INTLIFY_BUNDLE_IMPORT_ID === getVirtualId(id, meta.framework) && include) {
|
|
376
381
|
let resourcePaths = [];
|
|
377
382
|
for (const inc of include) {
|
|
@@ -381,7 +386,8 @@ function resourcePlugin({
|
|
|
381
386
|
const code = await generateBundleResources(resourcePaths, isProduction, {
|
|
382
387
|
forceStringify,
|
|
383
388
|
strictMessage,
|
|
384
|
-
escapeHtml
|
|
389
|
+
escapeHtml,
|
|
390
|
+
usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
385
391
|
});
|
|
386
392
|
return {
|
|
387
393
|
moduleType: "js",
|
|
@@ -398,7 +404,7 @@ function resourcePlugin({
|
|
|
398
404
|
return;
|
|
399
405
|
}
|
|
400
406
|
const { filename, query } = parseVueRequest(id);
|
|
401
|
-
debug$
|
|
407
|
+
debug$3("transform", id, JSON.stringify(query), filename);
|
|
402
408
|
let langInfo = defaultSFCLang;
|
|
403
409
|
let inSourceMap;
|
|
404
410
|
if (!query.vue) {
|
|
@@ -417,13 +423,14 @@ function resourcePlugin({
|
|
|
417
423
|
escapeHtml,
|
|
418
424
|
jit: true,
|
|
419
425
|
onlyLocales,
|
|
420
|
-
forceStringify
|
|
426
|
+
forceStringify,
|
|
427
|
+
usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
421
428
|
}
|
|
422
429
|
);
|
|
423
|
-
debug$
|
|
430
|
+
debug$3("parseOptions", parseOptions);
|
|
424
431
|
const { code: generatedCode, map } = await generate(code, parseOptions);
|
|
425
|
-
debug$
|
|
426
|
-
debug$
|
|
432
|
+
debug$3("generated code", generatedCode);
|
|
433
|
+
debug$3("sourcemap", map, sourceMap);
|
|
427
434
|
if (code === generatedCode) return;
|
|
428
435
|
return {
|
|
429
436
|
moduleType: "js",
|
|
@@ -438,8 +445,9 @@ function resourcePlugin({
|
|
|
438
445
|
} else if (defaultSFCLang) {
|
|
439
446
|
langInfo = defaultSFCLang;
|
|
440
447
|
}
|
|
441
|
-
debug$
|
|
448
|
+
debug$3("langInfo", langInfo);
|
|
442
449
|
const generate = /\.?json5?/.test(langInfo) ? generateJSON : generateYAML;
|
|
450
|
+
const isGlobalBlock = globalSFCScope || !!query.global;
|
|
443
451
|
const parseOptions = getOptions(
|
|
444
452
|
filename,
|
|
445
453
|
isProduction,
|
|
@@ -452,10 +460,11 @@ function resourcePlugin({
|
|
|
452
460
|
strictMessage,
|
|
453
461
|
escapeHtml,
|
|
454
462
|
onlyLocales,
|
|
455
|
-
forceStringify
|
|
463
|
+
forceStringify,
|
|
464
|
+
usedKeyFilter: collector && isGlobalBlock ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
456
465
|
}
|
|
457
466
|
);
|
|
458
|
-
debug$
|
|
467
|
+
debug$3("parseOptions", parseOptions);
|
|
459
468
|
let source = await getCode(
|
|
460
469
|
code,
|
|
461
470
|
filename,
|
|
@@ -473,8 +482,8 @@ function resourcePlugin({
|
|
|
473
482
|
}
|
|
474
483
|
}
|
|
475
484
|
const { code: generatedCode, map } = generate(source, parseOptions);
|
|
476
|
-
debug$
|
|
477
|
-
debug$
|
|
485
|
+
debug$3("generated code", generatedCode);
|
|
486
|
+
debug$3("sourcemap", map, sourceMap);
|
|
478
487
|
if (code === generatedCode) return;
|
|
479
488
|
return {
|
|
480
489
|
moduleType: "js",
|
|
@@ -530,11 +539,12 @@ async function generateBundleResources(resources, isProduction, {
|
|
|
530
539
|
strictMessage = true,
|
|
531
540
|
escapeHtml = false,
|
|
532
541
|
jit = true,
|
|
533
|
-
transformI18nBlock = void 0
|
|
542
|
+
transformI18nBlock = void 0,
|
|
543
|
+
usedKeyFilter = void 0
|
|
534
544
|
}) {
|
|
535
545
|
const codes = [];
|
|
536
546
|
for (const res of resources) {
|
|
537
|
-
debug$
|
|
547
|
+
debug$3(`${res} bundle loading ...`);
|
|
538
548
|
if (/\.(json5?|ya?ml)$/.test(res)) {
|
|
539
549
|
const { ext, name } = parse(res);
|
|
540
550
|
const source = await getRaw(res);
|
|
@@ -546,11 +556,12 @@ async function generateBundleResources(resources, isProduction, {
|
|
|
546
556
|
strictMessage,
|
|
547
557
|
escapeHtml,
|
|
548
558
|
forceStringify,
|
|
549
|
-
transformI18nBlock
|
|
559
|
+
transformI18nBlock,
|
|
560
|
+
usedKeyFilter
|
|
550
561
|
});
|
|
551
562
|
parseOptions.type = "bare";
|
|
552
563
|
const { code } = generate(source, parseOptions);
|
|
553
|
-
debug$
|
|
564
|
+
debug$3("generated code", code);
|
|
554
565
|
codes.push(`${JSON.stringify(name)}: ${code}`);
|
|
555
566
|
}
|
|
556
567
|
}
|
|
@@ -585,7 +596,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
|
|
|
585
596
|
}
|
|
586
597
|
if (framework === "webpack") {
|
|
587
598
|
if (issuerPath) {
|
|
588
|
-
debug$
|
|
599
|
+
debug$3(`getCode (webpack) ${index} via issuerPath`, issuerPath);
|
|
589
600
|
return await getRaw(filename);
|
|
590
601
|
} else {
|
|
591
602
|
const result = parser(await getRaw(filename), {
|
|
@@ -595,7 +606,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
|
|
|
595
606
|
const block = result.descriptor.customBlocks[index];
|
|
596
607
|
if (block) {
|
|
597
608
|
const code = block.src ? await getRaw(block.src) : block.content;
|
|
598
|
-
debug$
|
|
609
|
+
debug$3(`getCode (webpack) ${index} from SFC`, code);
|
|
599
610
|
return code;
|
|
600
611
|
} else {
|
|
601
612
|
return source;
|
|
@@ -619,7 +630,8 @@ function getOptions(filename, isProduction, query, sourceMap, {
|
|
|
619
630
|
strictMessage = true,
|
|
620
631
|
escapeHtml = false,
|
|
621
632
|
jit = true,
|
|
622
|
-
transformI18nBlock = null
|
|
633
|
+
transformI18nBlock = null,
|
|
634
|
+
usedKeyFilter = void 0
|
|
623
635
|
}) {
|
|
624
636
|
const mode = isProduction ? "production" : "development";
|
|
625
637
|
const baseOptions = {
|
|
@@ -632,6 +644,7 @@ function getOptions(filename, isProduction, query, sourceMap, {
|
|
|
632
644
|
escapeHtml,
|
|
633
645
|
jit,
|
|
634
646
|
onlyLocales,
|
|
647
|
+
usedKeyFilter,
|
|
635
648
|
env: mode,
|
|
636
649
|
transformI18nBlock,
|
|
637
650
|
onWarn: (msg) => {
|
|
@@ -676,7 +689,12 @@ async function getRaw(path) {
|
|
|
676
689
|
return promises.readFile(path, { encoding: "utf-8" });
|
|
677
690
|
}
|
|
678
691
|
|
|
679
|
-
const debug$
|
|
692
|
+
const debug$2 = createDebug(resolveNamespace("directive"));
|
|
693
|
+
const tsEstree = {
|
|
694
|
+
parse: void 0,
|
|
695
|
+
simpleTraverse: void 0,
|
|
696
|
+
AST_NODE_TYPES: void 0
|
|
697
|
+
};
|
|
680
698
|
function directivePlugin({
|
|
681
699
|
optimizeTranslationDirective,
|
|
682
700
|
translationIdentifiers
|
|
@@ -688,7 +706,13 @@ function directivePlugin({
|
|
|
688
706
|
name: resolveNamespace("directive"),
|
|
689
707
|
enforce: "pre",
|
|
690
708
|
vite: {
|
|
691
|
-
config(config) {
|
|
709
|
+
async config(config) {
|
|
710
|
+
await import('@typescript-eslint/typescript-estree').then((r) => {
|
|
711
|
+
tsEstree.parse = r.parse;
|
|
712
|
+
tsEstree.simpleTraverse = r.simpleTraverse;
|
|
713
|
+
tsEstree.AST_NODE_TYPES = r.AST_NODE_TYPES;
|
|
714
|
+
return;
|
|
715
|
+
});
|
|
692
716
|
vuePlugin = getVitePlugin(config, "vite:vue");
|
|
693
717
|
if (!checkVuePlugin(vuePlugin)) {
|
|
694
718
|
return;
|
|
@@ -699,7 +723,7 @@ function directivePlugin({
|
|
|
699
723
|
optimizeTranslationDirective,
|
|
700
724
|
translationIdentifiers
|
|
701
725
|
);
|
|
702
|
-
debug$
|
|
726
|
+
debug$2(`vite:vue options['template']:`, vuePlugin.api.options);
|
|
703
727
|
}
|
|
704
728
|
},
|
|
705
729
|
configResolved(config) {
|
|
@@ -742,7 +766,7 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
|
|
|
742
766
|
const vuePluginOptions = getVuePluginOptions(vuePlugin);
|
|
743
767
|
const normalizedFilename = normalizePath(path.relative(vuePluginOptions.root, filename));
|
|
744
768
|
const resolveIdentifier2 = translationIdentifiers.get(normalizedFilename);
|
|
745
|
-
debug$
|
|
769
|
+
debug$2("resolved vue-i18n Identifier", resolveIdentifier2);
|
|
746
770
|
if (resolveIdentifier2 == null) {
|
|
747
771
|
return void 0;
|
|
748
772
|
}
|
|
@@ -760,12 +784,12 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
|
|
|
760
784
|
}
|
|
761
785
|
function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
|
|
762
786
|
const source = descriptor.scriptSetup?.content || descriptor.script?.content;
|
|
763
|
-
debug$
|
|
787
|
+
debug$2("getDescriptor content", source);
|
|
764
788
|
if (!source) {
|
|
765
789
|
return;
|
|
766
790
|
}
|
|
767
|
-
const ast = parse
|
|
768
|
-
simpleTraverse(ast, {
|
|
791
|
+
const ast = tsEstree.parse(source, { range: true });
|
|
792
|
+
tsEstree.simpleTraverse(ast, {
|
|
769
793
|
enter(node, parent) {
|
|
770
794
|
if (parent) {
|
|
771
795
|
node.parent = parent;
|
|
@@ -778,11 +802,11 @@ function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
|
|
|
778
802
|
if (importLocalName == null) {
|
|
779
803
|
return;
|
|
780
804
|
}
|
|
781
|
-
debug$
|
|
805
|
+
debug$2("importLocalName", importLocalName);
|
|
782
806
|
const resolvedIdentifier = getVueI18nIdentifier(scope, importLocalName);
|
|
783
807
|
if (resolvedIdentifier) {
|
|
784
808
|
const normalizedFilename = normalizePath(path.relative(root, descriptor.filename));
|
|
785
|
-
debug$
|
|
809
|
+
debug$2("set vue-i18n resolved identifier: ", resolvedIdentifier);
|
|
786
810
|
translationIdentifiers.set(normalizedFilename, resolvedIdentifier);
|
|
787
811
|
}
|
|
788
812
|
}
|
|
@@ -830,9 +854,9 @@ function getVueI18nIdentifier(scope, local) {
|
|
|
830
854
|
return null;
|
|
831
855
|
}
|
|
832
856
|
const variableIdPairs = parseVariableId(id);
|
|
833
|
-
debug$
|
|
857
|
+
debug$2("variableIdPairs:", variableIdPairs);
|
|
834
858
|
const returnVariableIdPairs = parseReturnStatement(returnStatement);
|
|
835
|
-
debug$
|
|
859
|
+
debug$2("returnVariableIdPairs:", returnVariableIdPairs);
|
|
836
860
|
return resolveIdentifier(variableIdPairs, returnVariableIdPairs);
|
|
837
861
|
}
|
|
838
862
|
const EMPTY_NODE_RETURN = {
|
|
@@ -845,18 +869,18 @@ function getCallExpressionAndReturnStatement(scope, local) {
|
|
|
845
869
|
return EMPTY_NODE_RETURN;
|
|
846
870
|
}
|
|
847
871
|
const callExpressionRef = variable.references.find((ref) => {
|
|
848
|
-
return ref.identifier.parent?.type === AST_NODE_TYPES.CallExpression;
|
|
872
|
+
return ref.identifier.parent?.type === tsEstree.AST_NODE_TYPES.CallExpression;
|
|
849
873
|
});
|
|
850
874
|
if (callExpressionRef == null) {
|
|
851
875
|
return EMPTY_NODE_RETURN;
|
|
852
876
|
}
|
|
853
877
|
let returnStatement = null;
|
|
854
|
-
if (callExpressionRef.from.type === "function" && callExpressionRef.from.block.type === AST_NODE_TYPES.FunctionExpression && // @ts-expect-error -- FIXME: type error
|
|
855
|
-
callExpressionRef.from.block.parent.type === AST_NODE_TYPES.Property && // @ts-expect-error -- FIXME: type error
|
|
856
|
-
callExpressionRef.from.block.parent.key.type === AST_NODE_TYPES.Identifier && // @ts-expect-error -- FIXME: type error
|
|
878
|
+
if (callExpressionRef.from.type === "function" && callExpressionRef.from.block.type === tsEstree.AST_NODE_TYPES.FunctionExpression && // @ts-expect-error -- FIXME: type error
|
|
879
|
+
callExpressionRef.from.block.parent.type === tsEstree.AST_NODE_TYPES.Property && // @ts-expect-error -- FIXME: type error
|
|
880
|
+
callExpressionRef.from.block.parent.key.type === tsEstree.AST_NODE_TYPES.Identifier && // @ts-expect-error -- FIXME: type error
|
|
857
881
|
callExpressionRef.from.block.parent.key.name === "setup") {
|
|
858
882
|
returnStatement = callExpressionRef.from.block.body.body.find((statement) => {
|
|
859
|
-
return statement.type === AST_NODE_TYPES.ReturnStatement;
|
|
883
|
+
return statement.type === tsEstree.AST_NODE_TYPES.ReturnStatement;
|
|
860
884
|
});
|
|
861
885
|
}
|
|
862
886
|
return {
|
|
@@ -866,22 +890,22 @@ function getCallExpressionAndReturnStatement(scope, local) {
|
|
|
866
890
|
};
|
|
867
891
|
}
|
|
868
892
|
function getVariableDeclarationIdFrom(node) {
|
|
869
|
-
if (node.parent?.type !== AST_NODE_TYPES.VariableDeclarator) {
|
|
893
|
+
if (node.parent?.type !== tsEstree.AST_NODE_TYPES.VariableDeclarator) {
|
|
870
894
|
return null;
|
|
871
895
|
}
|
|
872
896
|
return node.parent.id;
|
|
873
897
|
}
|
|
874
898
|
function parseVariableId(node) {
|
|
875
|
-
if (node.type === AST_NODE_TYPES.Identifier) {
|
|
899
|
+
if (node.type === tsEstree.AST_NODE_TYPES.Identifier) {
|
|
876
900
|
return [{ key: node.name, value: null }];
|
|
877
901
|
} else {
|
|
878
902
|
const props = node.properties.filter(
|
|
879
903
|
// ignore RestElement
|
|
880
|
-
(prop) => prop.type === AST_NODE_TYPES.Property
|
|
904
|
+
(prop) => prop.type === tsEstree.AST_NODE_TYPES.Property
|
|
881
905
|
);
|
|
882
906
|
const pairs = [];
|
|
883
907
|
for (const prop of props) {
|
|
884
|
-
if (prop?.key.type === AST_NODE_TYPES.Identifier && prop?.value.type === AST_NODE_TYPES.Identifier) {
|
|
908
|
+
if (prop?.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop?.value.type === tsEstree.AST_NODE_TYPES.Identifier) {
|
|
885
909
|
pairs.push({ key: prop.key.name, value: prop.value.name });
|
|
886
910
|
}
|
|
887
911
|
}
|
|
@@ -893,12 +917,12 @@ function parseReturnStatement(node) {
|
|
|
893
917
|
if (node == null || node.argument == null) {
|
|
894
918
|
return pairs;
|
|
895
919
|
}
|
|
896
|
-
if (node.argument.type === AST_NODE_TYPES.ObjectExpression) {
|
|
920
|
+
if (node.argument.type === tsEstree.AST_NODE_TYPES.ObjectExpression) {
|
|
897
921
|
for (const prop of node.argument.properties) {
|
|
898
|
-
if (prop.type === AST_NODE_TYPES.Property) {
|
|
899
|
-
if (prop.key.type === AST_NODE_TYPES.Identifier && prop.value.type === AST_NODE_TYPES.Identifier) {
|
|
922
|
+
if (prop.type === tsEstree.AST_NODE_TYPES.Property) {
|
|
923
|
+
if (prop.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.type === tsEstree.AST_NODE_TYPES.Identifier) {
|
|
900
924
|
pairs.push({ key: prop.key.name, value: prop.value.name });
|
|
901
|
-
} else if (prop.key.type === AST_NODE_TYPES.Identifier && prop.value.type === AST_NODE_TYPES.MemberExpression && prop.value.object.type === AST_NODE_TYPES.Identifier && prop.value.property.type === AST_NODE_TYPES.Identifier) {
|
|
925
|
+
} else if (prop.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.type === tsEstree.AST_NODE_TYPES.MemberExpression && prop.value.object.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.property.type === tsEstree.AST_NODE_TYPES.Identifier) {
|
|
902
926
|
pairs.push({
|
|
903
927
|
key: prop.key.name,
|
|
904
928
|
value: `${prop.value.object.name}.${prop.value.property.name}`
|
|
@@ -907,7 +931,7 @@ function parseReturnStatement(node) {
|
|
|
907
931
|
}
|
|
908
932
|
}
|
|
909
933
|
return pairs;
|
|
910
|
-
} else if (node.argument.type === AST_NODE_TYPES.Identifier) {
|
|
934
|
+
} else if (node.argument.type === tsEstree.AST_NODE_TYPES.Identifier) {
|
|
911
935
|
return pairs;
|
|
912
936
|
} else {
|
|
913
937
|
return pairs;
|
|
@@ -944,6 +968,270 @@ function resolveIdentifier(localVariables, returnVariable) {
|
|
|
944
968
|
}
|
|
945
969
|
}
|
|
946
970
|
|
|
971
|
+
function matchSafelistPattern(keyPath, pattern) {
|
|
972
|
+
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^.]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
973
|
+
return new RegExp(`^${regexStr}$`).test(keyPath);
|
|
974
|
+
}
|
|
975
|
+
function createUsedKeysCollector(options) {
|
|
976
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
977
|
+
const safelistPatterns = options.safelist || [];
|
|
978
|
+
const dynamicKeyStrategy = options.dynamicKeyStrategy || "keep-all";
|
|
979
|
+
const removedKeys = /* @__PURE__ */ new Map();
|
|
980
|
+
let dynamicKeysDetected = false;
|
|
981
|
+
return {
|
|
982
|
+
get usedKeys() {
|
|
983
|
+
return usedKeys;
|
|
984
|
+
},
|
|
985
|
+
get dynamicKeysDetected() {
|
|
986
|
+
return dynamicKeysDetected;
|
|
987
|
+
},
|
|
988
|
+
set dynamicKeysDetected(val) {
|
|
989
|
+
dynamicKeysDetected = val;
|
|
990
|
+
},
|
|
991
|
+
safelistPatterns,
|
|
992
|
+
removedKeys,
|
|
993
|
+
addKey(key) {
|
|
994
|
+
usedKeys.add(key);
|
|
995
|
+
},
|
|
996
|
+
markDynamic() {
|
|
997
|
+
dynamicKeysDetected = true;
|
|
998
|
+
},
|
|
999
|
+
shouldKeepKey(keyPath) {
|
|
1000
|
+
if (dynamicKeysDetected && dynamicKeyStrategy === "keep-all") {
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
if (safelistPatterns.some((pattern) => matchSafelistPattern(keyPath, pattern))) {
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
if (usedKeys.has(keyPath)) {
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
1009
|
+
for (const key of usedKeys) {
|
|
1010
|
+
if (key.startsWith(keyPath + ".")) {
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return false;
|
|
1015
|
+
},
|
|
1016
|
+
reportRemoved(filename, keyPath) {
|
|
1017
|
+
const existing = removedKeys.get(filename);
|
|
1018
|
+
if (existing) {
|
|
1019
|
+
existing.push(keyPath);
|
|
1020
|
+
} else {
|
|
1021
|
+
removedKeys.set(filename, [keyPath]);
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
getDiagnostics() {
|
|
1025
|
+
let totalRemoved = 0;
|
|
1026
|
+
for (const keys of removedKeys.values()) {
|
|
1027
|
+
totalRemoved += keys.length;
|
|
1028
|
+
}
|
|
1029
|
+
return { totalRemoved, byFile: removedKeys };
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const I18N_FUNCTIONS = /* @__PURE__ */ new Set(["t", "$t", "tc", "$tc", "te", "$te", "d", "$d", "n", "$n"]);
|
|
1035
|
+
const TEMPLATE_T_REGEX = /\$?t\s*\(\s*['"]([^'"]+)['"]/g;
|
|
1036
|
+
const VT_DIRECTIVE_STRING_REGEX = /v-t\s*=\s*"'([^']+)'"/g;
|
|
1037
|
+
const VT_DIRECTIVE_PATH_REGEX = /v-t\s*=\s*"\{[^}]*path\s*:\s*'([^']+)'/g;
|
|
1038
|
+
const TEMPLATE_DYNAMIC_T_REGEX = /\$?t\s*\(\s*[a-zA-Z_$]/g;
|
|
1039
|
+
async function extractKeysFromScript(source) {
|
|
1040
|
+
const keys = [];
|
|
1041
|
+
let hasDynamic = false;
|
|
1042
|
+
let tsEstree;
|
|
1043
|
+
try {
|
|
1044
|
+
tsEstree = await import('@typescript-eslint/typescript-estree');
|
|
1045
|
+
} catch {
|
|
1046
|
+
return extractKeysFromScriptRegex(source);
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
const ast = tsEstree.parse(source, {
|
|
1050
|
+
range: true,
|
|
1051
|
+
jsx: true,
|
|
1052
|
+
allowInvalidAST: true,
|
|
1053
|
+
suppressDeprecatedPropertyWarnings: true
|
|
1054
|
+
});
|
|
1055
|
+
tsEstree.simpleTraverse(ast, {
|
|
1056
|
+
enter(node) {
|
|
1057
|
+
if (node.type !== tsEstree.AST_NODE_TYPES.CallExpression) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const calleeName = getCalleeName(node, tsEstree.AST_NODE_TYPES);
|
|
1061
|
+
if (!calleeName || !I18N_FUNCTIONS.has(calleeName)) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const firstArg = node.arguments[0];
|
|
1065
|
+
if (!firstArg) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (firstArg.type === tsEstree.AST_NODE_TYPES.Literal && typeof firstArg.value === "string") {
|
|
1069
|
+
keys.push(firstArg.value);
|
|
1070
|
+
} else if (firstArg.type === tsEstree.AST_NODE_TYPES.TemplateLiteral && firstArg.expressions.length === 0 && firstArg.quasis.length === 1) {
|
|
1071
|
+
const value = firstArg.quasis[0].value.cooked;
|
|
1072
|
+
if (value) {
|
|
1073
|
+
keys.push(value);
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
hasDynamic = true;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
} catch {
|
|
1081
|
+
return extractKeysFromScriptRegex(source);
|
|
1082
|
+
}
|
|
1083
|
+
return { keys, hasDynamic };
|
|
1084
|
+
}
|
|
1085
|
+
function getCalleeName(node, AST_NODE_TYPES) {
|
|
1086
|
+
const callee = node.callee;
|
|
1087
|
+
if (callee.type === AST_NODE_TYPES.Identifier) {
|
|
1088
|
+
return callee.name;
|
|
1089
|
+
}
|
|
1090
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier) {
|
|
1091
|
+
return callee.property.name;
|
|
1092
|
+
}
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
function extractKeysFromScriptRegex(source) {
|
|
1096
|
+
const keys = [];
|
|
1097
|
+
let match;
|
|
1098
|
+
const regex = /\b(?:\$?t|tc|\$tc|te|\$te|d|\$d|n|\$n)\s*\(\s*['"]([^'"]+)['"]/g;
|
|
1099
|
+
while ((match = regex.exec(source)) !== null) {
|
|
1100
|
+
keys.push(match[1]);
|
|
1101
|
+
}
|
|
1102
|
+
return { keys, hasDynamic: false };
|
|
1103
|
+
}
|
|
1104
|
+
function extractKeysFromTemplate(templateContent) {
|
|
1105
|
+
const keys = [];
|
|
1106
|
+
let hasDynamic = false;
|
|
1107
|
+
let match;
|
|
1108
|
+
TEMPLATE_T_REGEX.lastIndex = 0;
|
|
1109
|
+
VT_DIRECTIVE_STRING_REGEX.lastIndex = 0;
|
|
1110
|
+
VT_DIRECTIVE_PATH_REGEX.lastIndex = 0;
|
|
1111
|
+
TEMPLATE_DYNAMIC_T_REGEX.lastIndex = 0;
|
|
1112
|
+
while ((match = TEMPLATE_T_REGEX.exec(templateContent)) !== null) {
|
|
1113
|
+
keys.push(match[1]);
|
|
1114
|
+
}
|
|
1115
|
+
while ((match = VT_DIRECTIVE_STRING_REGEX.exec(templateContent)) !== null) {
|
|
1116
|
+
keys.push(match[1]);
|
|
1117
|
+
}
|
|
1118
|
+
while ((match = VT_DIRECTIVE_PATH_REGEX.exec(templateContent)) !== null) {
|
|
1119
|
+
keys.push(match[1]);
|
|
1120
|
+
}
|
|
1121
|
+
if (TEMPLATE_DYNAMIC_T_REGEX.test(templateContent)) {
|
|
1122
|
+
hasDynamic = true;
|
|
1123
|
+
}
|
|
1124
|
+
return { keys, hasDynamic };
|
|
1125
|
+
}
|
|
1126
|
+
async function analyzeVueSFC(content) {
|
|
1127
|
+
const keys = [];
|
|
1128
|
+
let hasDynamic = false;
|
|
1129
|
+
let parse;
|
|
1130
|
+
try {
|
|
1131
|
+
const compilerSfc = await import('vue/compiler-sfc');
|
|
1132
|
+
parse = compilerSfc.parse;
|
|
1133
|
+
} catch {
|
|
1134
|
+
const templateResult = extractKeysFromTemplate(content);
|
|
1135
|
+
const scriptResult = await extractKeysFromScript(content);
|
|
1136
|
+
return {
|
|
1137
|
+
keys: [...templateResult.keys, ...scriptResult.keys],
|
|
1138
|
+
hasDynamic: templateResult.hasDynamic || scriptResult.hasDynamic
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
const { descriptor } = parse(content);
|
|
1142
|
+
if (descriptor.template?.content) {
|
|
1143
|
+
const templateResult = extractKeysFromTemplate(descriptor.template.content);
|
|
1144
|
+
keys.push(...templateResult.keys);
|
|
1145
|
+
if (templateResult.hasDynamic) {
|
|
1146
|
+
hasDynamic = true;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const scriptContent = descriptor.scriptSetup?.content || descriptor.script?.content;
|
|
1150
|
+
if (scriptContent) {
|
|
1151
|
+
const scriptResult = await extractKeysFromScript(scriptContent);
|
|
1152
|
+
keys.push(...scriptResult.keys);
|
|
1153
|
+
if (scriptResult.hasDynamic) {
|
|
1154
|
+
hasDynamic = true;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return { keys, hasDynamic };
|
|
1158
|
+
}
|
|
1159
|
+
async function analyzeFile(content, filePath, collector) {
|
|
1160
|
+
let result;
|
|
1161
|
+
if (filePath.endsWith(".vue")) {
|
|
1162
|
+
result = await analyzeVueSFC(content);
|
|
1163
|
+
} else {
|
|
1164
|
+
result = await extractKeysFromScript(content);
|
|
1165
|
+
}
|
|
1166
|
+
for (const key of result.keys) {
|
|
1167
|
+
collector.addKey(key);
|
|
1168
|
+
}
|
|
1169
|
+
if (result.hasDynamic) {
|
|
1170
|
+
collector.markDynamic();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const debug$1 = createDebug(resolveNamespace("tree-shaking"));
|
|
1175
|
+
function treeShakingPlugin(resolvedOptions, collector) {
|
|
1176
|
+
let projectRoot = "";
|
|
1177
|
+
return {
|
|
1178
|
+
name: resolveNamespace("tree-shaking"),
|
|
1179
|
+
enforce: "pre",
|
|
1180
|
+
vite: {
|
|
1181
|
+
configResolved(config) {
|
|
1182
|
+
projectRoot = config.root;
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
webpack(compiler) {
|
|
1186
|
+
projectRoot = compiler.options.context || process.cwd();
|
|
1187
|
+
},
|
|
1188
|
+
async buildStart() {
|
|
1189
|
+
const treeShaking = resolvedOptions.treeShaking;
|
|
1190
|
+
const patterns = treeShaking?.scanPatterns || [`${projectRoot}/src/**/*.{vue,ts,js,tsx,jsx}`];
|
|
1191
|
+
debug$1("scanning patterns:", patterns);
|
|
1192
|
+
const files = await fg(patterns, {
|
|
1193
|
+
ignore: ["**/node_modules/**"],
|
|
1194
|
+
absolute: true
|
|
1195
|
+
});
|
|
1196
|
+
debug$1(`found ${files.length} source files to scan`);
|
|
1197
|
+
for (const file of files) {
|
|
1198
|
+
try {
|
|
1199
|
+
const content = await promises.readFile(file, "utf-8");
|
|
1200
|
+
await analyzeFile(content, file, collector);
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
debug$1(`failed to analyze file: ${file}`, err);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
debug$1(
|
|
1206
|
+
`scan complete: ${collector.usedKeys.size} used keys found, dynamic: ${collector.dynamicKeysDetected}`
|
|
1207
|
+
);
|
|
1208
|
+
if (collector.dynamicKeysDetected) {
|
|
1209
|
+
const strategy = treeShaking?.dynamicKeyStrategy || "keep-all";
|
|
1210
|
+
if (strategy === "keep-all") {
|
|
1211
|
+
warn(
|
|
1212
|
+
"Tree-shaking: dynamic key usage detected. All keys will be preserved (dynamicKeyStrategy: keep-all)."
|
|
1213
|
+
);
|
|
1214
|
+
} else {
|
|
1215
|
+
warn(
|
|
1216
|
+
"Tree-shaking: dynamic key usage detected. Unused keys will still be removed (dynamicKeyStrategy: ignore)."
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1221
|
+
buildEnd() {
|
|
1222
|
+
const diagnostics = collector.getDiagnostics();
|
|
1223
|
+
if (diagnostics.totalRemoved > 0) {
|
|
1224
|
+
debug$1(`tree-shaking: removed ${diagnostics.totalRemoved} unused message keys`);
|
|
1225
|
+
for (const [file, keys] of diagnostics.byFile) {
|
|
1226
|
+
debug$1(` ${file}: removed ${keys.length} keys (${keys.join(", ")})`);
|
|
1227
|
+
}
|
|
1228
|
+
} else {
|
|
1229
|
+
debug$1("tree-shaking: no keys removed");
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
947
1235
|
const debug = createDebug(resolveNamespace("root"));
|
|
948
1236
|
const unpluginFactory = (options = {}, meta) => {
|
|
949
1237
|
debug("meta framework", meta.framework);
|
|
@@ -953,7 +1241,12 @@ const unpluginFactory = (options = {}, meta) => {
|
|
|
953
1241
|
debug("plugin options (resolving):", options);
|
|
954
1242
|
const resolvedOptions = resolveOptions(options);
|
|
955
1243
|
debug("plugin options (resolved):", resolvedOptions);
|
|
956
|
-
const
|
|
1244
|
+
const collector = resolvedOptions.treeShaking ? createUsedKeysCollector(resolvedOptions.treeShaking) : null;
|
|
1245
|
+
const plugins = [];
|
|
1246
|
+
if (resolvedOptions.treeShaking && collector) {
|
|
1247
|
+
plugins.push(treeShakingPlugin(resolvedOptions, collector));
|
|
1248
|
+
}
|
|
1249
|
+
plugins.push(resourcePlugin(resolvedOptions, meta, collector));
|
|
957
1250
|
if (resolvedOptions.optimizeTranslationDirective) {
|
|
958
1251
|
if (meta.framework === "webpack") {
|
|
959
1252
|
raiseError(
|