@intlify/unplugin-vue-i18n 11.0.7 → 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 +328 -45
- 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 +328 -45
- package/lib/types.d.cts +26 -1
- package/lib/types.d.mts +26 -1
- package/lib/types.d.ts +26 -1
- package/package.json +2 -2
package/lib/index.cjs
CHANGED
|
@@ -49,6 +49,11 @@ function resolveOptions(options) {
|
|
|
49
49
|
const optimizeTranslationDirective = shared.isString(options.optimizeTranslationDirective) || shared.isArray(options.optimizeTranslationDirective) ? options.optimizeTranslationDirective : !!options.optimizeTranslationDirective;
|
|
50
50
|
const translationIdentifiers = /* @__PURE__ */ new Map();
|
|
51
51
|
const transformI18nBlock = typeof options.transformI18nBlock === "function" ? options.transformI18nBlock : null;
|
|
52
|
+
const treeShaking = options.treeShaking ? typeof options.treeShaking === "object" ? {
|
|
53
|
+
safelist: options.treeShaking.safelist || [],
|
|
54
|
+
dynamicKeyStrategy: options.treeShaking.dynamicKeyStrategy || "keep-all",
|
|
55
|
+
scanPatterns: options.treeShaking.scanPatterns
|
|
56
|
+
} : { safelist: [], dynamicKeyStrategy: "keep-all" } : null;
|
|
52
57
|
return {
|
|
53
58
|
include: normalizeGlobOption(options.include || []),
|
|
54
59
|
exclude: normalizeGlobOption(options.exclude),
|
|
@@ -67,7 +72,8 @@ function resolveOptions(options) {
|
|
|
67
72
|
escapeHtml,
|
|
68
73
|
optimizeTranslationDirective,
|
|
69
74
|
translationIdentifiers,
|
|
70
|
-
transformI18nBlock
|
|
75
|
+
transformI18nBlock,
|
|
76
|
+
treeShaking
|
|
71
77
|
};
|
|
72
78
|
}
|
|
73
79
|
|
|
@@ -179,7 +185,7 @@ const RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID = new RegExp(
|
|
|
179
185
|
const RE_RESOURCE_FORMAT = /\.(json5?|ya?ml|[c|m]?[j|t]s)$/;
|
|
180
186
|
const RE_SFC_I18N_CUSTOM_BLOCK = /\?vue&type=i18n/;
|
|
181
187
|
const RE_SFC_I18N_WEBPACK_CUSTOM_BLOCK = /blockType=i18n/;
|
|
182
|
-
const debug$
|
|
188
|
+
const debug$3 = createDebug__default(resolveNamespace("resource"));
|
|
183
189
|
function resourcePlugin({
|
|
184
190
|
onlyLocales,
|
|
185
191
|
include,
|
|
@@ -197,7 +203,7 @@ function resourcePlugin({
|
|
|
197
203
|
allowDynamic,
|
|
198
204
|
escapeHtml,
|
|
199
205
|
transformI18nBlock
|
|
200
|
-
}, meta) {
|
|
206
|
+
}, meta, collector) {
|
|
201
207
|
let viteModule = null;
|
|
202
208
|
async function getViteModule() {
|
|
203
209
|
if (viteModule != null) {
|
|
@@ -225,15 +231,15 @@ function resourcePlugin({
|
|
|
225
231
|
return _filter;
|
|
226
232
|
}
|
|
227
233
|
if (meta.framework == "webpack") {
|
|
228
|
-
debug$
|
|
234
|
+
debug$3("Using filter for webpack");
|
|
229
235
|
_filter = pluginutils.createFilter(...resolveIncludeExclude());
|
|
230
236
|
} else {
|
|
231
237
|
const viteModule2 = await getViteModule();
|
|
232
238
|
if (viteModule2.rolldownVersion) {
|
|
233
|
-
debug$
|
|
239
|
+
debug$3("Using filter for rolldown-vite");
|
|
234
240
|
_filter = pluginutils.createFilter(...resolveIncludeExclude());
|
|
235
241
|
} else {
|
|
236
|
-
debug$
|
|
242
|
+
debug$3("Using filter for rollup-vite");
|
|
237
243
|
_filter = pluginutils.createFilter(...resolveIncludeExcludeForLegacy());
|
|
238
244
|
}
|
|
239
245
|
}
|
|
@@ -245,7 +251,7 @@ function resourcePlugin({
|
|
|
245
251
|
let isProduction = false;
|
|
246
252
|
let sourceMap = false;
|
|
247
253
|
const vueI18nAliasName = module;
|
|
248
|
-
debug$
|
|
254
|
+
debug$3(`vue-i18n alias name: ${vueI18nAliasName}`);
|
|
249
255
|
let vuePlugin = null;
|
|
250
256
|
const getSfcParser = () => {
|
|
251
257
|
return vuePlugin ? getVueCompiler(vuePlugin).parse : compilerSfc.parse;
|
|
@@ -271,7 +277,7 @@ function resourcePlugin({
|
|
|
271
277
|
__VUE_I18N_PROD_DEVTOOLS__: false
|
|
272
278
|
}
|
|
273
279
|
};
|
|
274
|
-
debug$
|
|
280
|
+
debug$3("define Config:", defineConfig);
|
|
275
281
|
const aliasConfig = {
|
|
276
282
|
resolve: {
|
|
277
283
|
alias: {
|
|
@@ -282,7 +288,7 @@ function resourcePlugin({
|
|
|
282
288
|
}
|
|
283
289
|
}
|
|
284
290
|
};
|
|
285
|
-
debug$
|
|
291
|
+
debug$3("alias Config:", aliasConfig);
|
|
286
292
|
return shared.assign(defineConfig, aliasConfig);
|
|
287
293
|
},
|
|
288
294
|
async configResolved(config) {
|
|
@@ -292,9 +298,9 @@ function resourcePlugin({
|
|
|
292
298
|
}
|
|
293
299
|
isProduction = config.isProduction;
|
|
294
300
|
sourceMap = config.command === "build" ? !!config.build.sourcemap : false;
|
|
295
|
-
debug$
|
|
301
|
+
debug$3(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
|
|
296
302
|
const isRolldownVite = !!(await getViteModule()).rolldownVersion;
|
|
297
|
-
debug$
|
|
303
|
+
debug$3(`Using ${isRolldownVite ? "rolldown-vite" : "vite"} for the build`);
|
|
298
304
|
if (!isRolldownVite) {
|
|
299
305
|
const jsonPlugin = getVitePlugin(config, "vite:json");
|
|
300
306
|
if (jsonPlugin) {
|
|
@@ -308,7 +314,7 @@ function resourcePlugin({
|
|
|
308
314
|
if (query.vue) {
|
|
309
315
|
return;
|
|
310
316
|
}
|
|
311
|
-
debug$
|
|
317
|
+
debug$3("org json plugin");
|
|
312
318
|
return orgTransform.apply(this, [code, id]);
|
|
313
319
|
}
|
|
314
320
|
jsonPlugin.transform = overrideJson;
|
|
@@ -333,7 +339,7 @@ function resourcePlugin({
|
|
|
333
339
|
webpack(compiler) {
|
|
334
340
|
isProduction = compiler.options.mode !== "development";
|
|
335
341
|
sourceMap = !!compiler.options.devtool;
|
|
336
|
-
debug$
|
|
342
|
+
debug$3(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
|
|
337
343
|
compiler.options.resolve = normalizeConfigResolveAlias(
|
|
338
344
|
compiler.options.resolve,
|
|
339
345
|
meta.framework
|
|
@@ -342,7 +348,7 @@ function resourcePlugin({
|
|
|
342
348
|
ssr: ssrBuild,
|
|
343
349
|
runtimeOnly
|
|
344
350
|
});
|
|
345
|
-
debug$
|
|
351
|
+
debug$3(
|
|
346
352
|
`set ${vueI18nAliasName}: ${getVueI18nAliasPath({
|
|
347
353
|
ssr: ssrBuild,
|
|
348
354
|
runtimeOnly
|
|
@@ -355,15 +361,15 @@ function resourcePlugin({
|
|
|
355
361
|
__INTLIFY_PROD_DEVTOOLS__: "false"
|
|
356
362
|
})
|
|
357
363
|
);
|
|
358
|
-
debug$
|
|
359
|
-
debug$
|
|
364
|
+
debug$3(`set __VUE_I18N_LEGACY_API__ is '${compositionOnly}'`);
|
|
365
|
+
debug$3(`set __VUE_I18N_FULL_INSTALL__ is '${fullInstall}'`);
|
|
360
366
|
const filter = pluginutils.createFilter(...resolveIncludeExclude());
|
|
361
367
|
if (compiler.options.module) {
|
|
362
368
|
compiler.options.module.rules.push({
|
|
363
369
|
test: RE_RESOURCE_FORMAT,
|
|
364
370
|
type: "javascript/auto",
|
|
365
371
|
include(resource) {
|
|
366
|
-
debug$
|
|
372
|
+
debug$3("webpack resource include", resource);
|
|
367
373
|
return filter(resource);
|
|
368
374
|
}
|
|
369
375
|
});
|
|
@@ -382,7 +388,7 @@ function resourcePlugin({
|
|
|
382
388
|
id: RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID
|
|
383
389
|
},
|
|
384
390
|
async handler(id) {
|
|
385
|
-
debug$
|
|
391
|
+
debug$3("load", id);
|
|
386
392
|
if (INTLIFY_BUNDLE_IMPORT_ID === getVirtualId(id, meta.framework) && include) {
|
|
387
393
|
let resourcePaths = [];
|
|
388
394
|
for (const inc of include) {
|
|
@@ -392,7 +398,8 @@ function resourcePlugin({
|
|
|
392
398
|
const code = await generateBundleResources(resourcePaths, isProduction, {
|
|
393
399
|
forceStringify,
|
|
394
400
|
strictMessage,
|
|
395
|
-
escapeHtml
|
|
401
|
+
escapeHtml,
|
|
402
|
+
usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
396
403
|
});
|
|
397
404
|
return {
|
|
398
405
|
moduleType: "js",
|
|
@@ -409,7 +416,7 @@ function resourcePlugin({
|
|
|
409
416
|
return;
|
|
410
417
|
}
|
|
411
418
|
const { filename, query } = parseVueRequest(id);
|
|
412
|
-
debug$
|
|
419
|
+
debug$3("transform", id, JSON.stringify(query), filename);
|
|
413
420
|
let langInfo = defaultSFCLang;
|
|
414
421
|
let inSourceMap;
|
|
415
422
|
if (!query.vue) {
|
|
@@ -428,13 +435,14 @@ function resourcePlugin({
|
|
|
428
435
|
escapeHtml,
|
|
429
436
|
jit: true,
|
|
430
437
|
onlyLocales,
|
|
431
|
-
forceStringify
|
|
438
|
+
forceStringify,
|
|
439
|
+
usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
432
440
|
}
|
|
433
441
|
);
|
|
434
|
-
debug$
|
|
442
|
+
debug$3("parseOptions", parseOptions);
|
|
435
443
|
const { code: generatedCode, map } = await generate(code, parseOptions);
|
|
436
|
-
debug$
|
|
437
|
-
debug$
|
|
444
|
+
debug$3("generated code", generatedCode);
|
|
445
|
+
debug$3("sourcemap", map, sourceMap);
|
|
438
446
|
if (code === generatedCode) return;
|
|
439
447
|
return {
|
|
440
448
|
moduleType: "js",
|
|
@@ -449,8 +457,9 @@ function resourcePlugin({
|
|
|
449
457
|
} else if (defaultSFCLang) {
|
|
450
458
|
langInfo = defaultSFCLang;
|
|
451
459
|
}
|
|
452
|
-
debug$
|
|
460
|
+
debug$3("langInfo", langInfo);
|
|
453
461
|
const generate = /\.?json5?/.test(langInfo) ? bundleUtils.generateJSON : bundleUtils.generateYAML;
|
|
462
|
+
const isGlobalBlock = globalSFCScope || !!query.global;
|
|
454
463
|
const parseOptions = getOptions(
|
|
455
464
|
filename,
|
|
456
465
|
isProduction,
|
|
@@ -463,10 +472,11 @@ function resourcePlugin({
|
|
|
463
472
|
strictMessage,
|
|
464
473
|
escapeHtml,
|
|
465
474
|
onlyLocales,
|
|
466
|
-
forceStringify
|
|
475
|
+
forceStringify,
|
|
476
|
+
usedKeyFilter: collector && isGlobalBlock ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
|
|
467
477
|
}
|
|
468
478
|
);
|
|
469
|
-
debug$
|
|
479
|
+
debug$3("parseOptions", parseOptions);
|
|
470
480
|
let source = await getCode(
|
|
471
481
|
code,
|
|
472
482
|
filename,
|
|
@@ -484,8 +494,8 @@ function resourcePlugin({
|
|
|
484
494
|
}
|
|
485
495
|
}
|
|
486
496
|
const { code: generatedCode, map } = generate(source, parseOptions);
|
|
487
|
-
debug$
|
|
488
|
-
debug$
|
|
497
|
+
debug$3("generated code", generatedCode);
|
|
498
|
+
debug$3("sourcemap", map, sourceMap);
|
|
489
499
|
if (code === generatedCode) return;
|
|
490
500
|
return {
|
|
491
501
|
moduleType: "js",
|
|
@@ -541,11 +551,12 @@ async function generateBundleResources(resources, isProduction, {
|
|
|
541
551
|
strictMessage = true,
|
|
542
552
|
escapeHtml = false,
|
|
543
553
|
jit = true,
|
|
544
|
-
transformI18nBlock = void 0
|
|
554
|
+
transformI18nBlock = void 0,
|
|
555
|
+
usedKeyFilter = void 0
|
|
545
556
|
}) {
|
|
546
557
|
const codes = [];
|
|
547
558
|
for (const res of resources) {
|
|
548
|
-
debug$
|
|
559
|
+
debug$3(`${res} bundle loading ...`);
|
|
549
560
|
if (/\.(json5?|ya?ml)$/.test(res)) {
|
|
550
561
|
const { ext, name } = path.parse(res);
|
|
551
562
|
const source = await getRaw(res);
|
|
@@ -557,11 +568,12 @@ async function generateBundleResources(resources, isProduction, {
|
|
|
557
568
|
strictMessage,
|
|
558
569
|
escapeHtml,
|
|
559
570
|
forceStringify,
|
|
560
|
-
transformI18nBlock
|
|
571
|
+
transformI18nBlock,
|
|
572
|
+
usedKeyFilter
|
|
561
573
|
});
|
|
562
574
|
parseOptions.type = "bare";
|
|
563
575
|
const { code } = generate(source, parseOptions);
|
|
564
|
-
debug$
|
|
576
|
+
debug$3("generated code", code);
|
|
565
577
|
codes.push(`${JSON.stringify(name)}: ${code}`);
|
|
566
578
|
}
|
|
567
579
|
}
|
|
@@ -596,7 +608,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
|
|
|
596
608
|
}
|
|
597
609
|
if (framework === "webpack") {
|
|
598
610
|
if (issuerPath) {
|
|
599
|
-
debug$
|
|
611
|
+
debug$3(`getCode (webpack) ${index} via issuerPath`, issuerPath);
|
|
600
612
|
return await getRaw(filename);
|
|
601
613
|
} else {
|
|
602
614
|
const result = parser(await getRaw(filename), {
|
|
@@ -606,7 +618,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
|
|
|
606
618
|
const block = result.descriptor.customBlocks[index];
|
|
607
619
|
if (block) {
|
|
608
620
|
const code = block.src ? await getRaw(block.src) : block.content;
|
|
609
|
-
debug$
|
|
621
|
+
debug$3(`getCode (webpack) ${index} from SFC`, code);
|
|
610
622
|
return code;
|
|
611
623
|
} else {
|
|
612
624
|
return source;
|
|
@@ -630,7 +642,8 @@ function getOptions(filename, isProduction, query, sourceMap, {
|
|
|
630
642
|
strictMessage = true,
|
|
631
643
|
escapeHtml = false,
|
|
632
644
|
jit = true,
|
|
633
|
-
transformI18nBlock = null
|
|
645
|
+
transformI18nBlock = null,
|
|
646
|
+
usedKeyFilter = void 0
|
|
634
647
|
}) {
|
|
635
648
|
const mode = isProduction ? "production" : "development";
|
|
636
649
|
const baseOptions = {
|
|
@@ -643,6 +656,7 @@ function getOptions(filename, isProduction, query, sourceMap, {
|
|
|
643
656
|
escapeHtml,
|
|
644
657
|
jit,
|
|
645
658
|
onlyLocales,
|
|
659
|
+
usedKeyFilter,
|
|
646
660
|
env: mode,
|
|
647
661
|
transformI18nBlock,
|
|
648
662
|
onWarn: (msg) => {
|
|
@@ -687,7 +701,7 @@ async function getRaw(path) {
|
|
|
687
701
|
return node_fs.promises.readFile(path, { encoding: "utf-8" });
|
|
688
702
|
}
|
|
689
703
|
|
|
690
|
-
const debug$
|
|
704
|
+
const debug$2 = createDebug__default(resolveNamespace("directive"));
|
|
691
705
|
const tsEstree = {
|
|
692
706
|
parse: void 0,
|
|
693
707
|
simpleTraverse: void 0,
|
|
@@ -721,7 +735,7 @@ function directivePlugin({
|
|
|
721
735
|
optimizeTranslationDirective,
|
|
722
736
|
translationIdentifiers
|
|
723
737
|
);
|
|
724
|
-
debug$
|
|
738
|
+
debug$2(`vite:vue options['template']:`, vuePlugin.api.options);
|
|
725
739
|
}
|
|
726
740
|
},
|
|
727
741
|
configResolved(config) {
|
|
@@ -764,7 +778,7 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
|
|
|
764
778
|
const vuePluginOptions = getVuePluginOptions(vuePlugin);
|
|
765
779
|
const normalizedFilename = normalizePath(path__default.relative(vuePluginOptions.root, filename));
|
|
766
780
|
const resolveIdentifier2 = translationIdentifiers.get(normalizedFilename);
|
|
767
|
-
debug$
|
|
781
|
+
debug$2("resolved vue-i18n Identifier", resolveIdentifier2);
|
|
768
782
|
if (resolveIdentifier2 == null) {
|
|
769
783
|
return void 0;
|
|
770
784
|
}
|
|
@@ -782,7 +796,7 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
|
|
|
782
796
|
}
|
|
783
797
|
function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
|
|
784
798
|
const source = descriptor.scriptSetup?.content || descriptor.script?.content;
|
|
785
|
-
debug$
|
|
799
|
+
debug$2("getDescriptor content", source);
|
|
786
800
|
if (!source) {
|
|
787
801
|
return;
|
|
788
802
|
}
|
|
@@ -800,11 +814,11 @@ function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
|
|
|
800
814
|
if (importLocalName == null) {
|
|
801
815
|
return;
|
|
802
816
|
}
|
|
803
|
-
debug$
|
|
817
|
+
debug$2("importLocalName", importLocalName);
|
|
804
818
|
const resolvedIdentifier = getVueI18nIdentifier(scope, importLocalName);
|
|
805
819
|
if (resolvedIdentifier) {
|
|
806
820
|
const normalizedFilename = normalizePath(path__default.relative(root, descriptor.filename));
|
|
807
|
-
debug$
|
|
821
|
+
debug$2("set vue-i18n resolved identifier: ", resolvedIdentifier);
|
|
808
822
|
translationIdentifiers.set(normalizedFilename, resolvedIdentifier);
|
|
809
823
|
}
|
|
810
824
|
}
|
|
@@ -852,9 +866,9 @@ function getVueI18nIdentifier(scope, local) {
|
|
|
852
866
|
return null;
|
|
853
867
|
}
|
|
854
868
|
const variableIdPairs = parseVariableId(id);
|
|
855
|
-
debug$
|
|
869
|
+
debug$2("variableIdPairs:", variableIdPairs);
|
|
856
870
|
const returnVariableIdPairs = parseReturnStatement(returnStatement);
|
|
857
|
-
debug$
|
|
871
|
+
debug$2("returnVariableIdPairs:", returnVariableIdPairs);
|
|
858
872
|
return resolveIdentifier(variableIdPairs, returnVariableIdPairs);
|
|
859
873
|
}
|
|
860
874
|
const EMPTY_NODE_RETURN = {
|
|
@@ -966,6 +980,270 @@ function resolveIdentifier(localVariables, returnVariable) {
|
|
|
966
980
|
}
|
|
967
981
|
}
|
|
968
982
|
|
|
983
|
+
function matchSafelistPattern(keyPath, pattern) {
|
|
984
|
+
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^.]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
985
|
+
return new RegExp(`^${regexStr}$`).test(keyPath);
|
|
986
|
+
}
|
|
987
|
+
function createUsedKeysCollector(options) {
|
|
988
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
989
|
+
const safelistPatterns = options.safelist || [];
|
|
990
|
+
const dynamicKeyStrategy = options.dynamicKeyStrategy || "keep-all";
|
|
991
|
+
const removedKeys = /* @__PURE__ */ new Map();
|
|
992
|
+
let dynamicKeysDetected = false;
|
|
993
|
+
return {
|
|
994
|
+
get usedKeys() {
|
|
995
|
+
return usedKeys;
|
|
996
|
+
},
|
|
997
|
+
get dynamicKeysDetected() {
|
|
998
|
+
return dynamicKeysDetected;
|
|
999
|
+
},
|
|
1000
|
+
set dynamicKeysDetected(val) {
|
|
1001
|
+
dynamicKeysDetected = val;
|
|
1002
|
+
},
|
|
1003
|
+
safelistPatterns,
|
|
1004
|
+
removedKeys,
|
|
1005
|
+
addKey(key) {
|
|
1006
|
+
usedKeys.add(key);
|
|
1007
|
+
},
|
|
1008
|
+
markDynamic() {
|
|
1009
|
+
dynamicKeysDetected = true;
|
|
1010
|
+
},
|
|
1011
|
+
shouldKeepKey(keyPath) {
|
|
1012
|
+
if (dynamicKeysDetected && dynamicKeyStrategy === "keep-all") {
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
if (safelistPatterns.some((pattern) => matchSafelistPattern(keyPath, pattern))) {
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
if (usedKeys.has(keyPath)) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
for (const key of usedKeys) {
|
|
1022
|
+
if (key.startsWith(keyPath + ".")) {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return false;
|
|
1027
|
+
},
|
|
1028
|
+
reportRemoved(filename, keyPath) {
|
|
1029
|
+
const existing = removedKeys.get(filename);
|
|
1030
|
+
if (existing) {
|
|
1031
|
+
existing.push(keyPath);
|
|
1032
|
+
} else {
|
|
1033
|
+
removedKeys.set(filename, [keyPath]);
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
getDiagnostics() {
|
|
1037
|
+
let totalRemoved = 0;
|
|
1038
|
+
for (const keys of removedKeys.values()) {
|
|
1039
|
+
totalRemoved += keys.length;
|
|
1040
|
+
}
|
|
1041
|
+
return { totalRemoved, byFile: removedKeys };
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const I18N_FUNCTIONS = /* @__PURE__ */ new Set(["t", "$t", "tc", "$tc", "te", "$te", "d", "$d", "n", "$n"]);
|
|
1047
|
+
const TEMPLATE_T_REGEX = /\$?t\s*\(\s*['"]([^'"]+)['"]/g;
|
|
1048
|
+
const VT_DIRECTIVE_STRING_REGEX = /v-t\s*=\s*"'([^']+)'"/g;
|
|
1049
|
+
const VT_DIRECTIVE_PATH_REGEX = /v-t\s*=\s*"\{[^}]*path\s*:\s*'([^']+)'/g;
|
|
1050
|
+
const TEMPLATE_DYNAMIC_T_REGEX = /\$?t\s*\(\s*[a-zA-Z_$]/g;
|
|
1051
|
+
async function extractKeysFromScript(source) {
|
|
1052
|
+
const keys = [];
|
|
1053
|
+
let hasDynamic = false;
|
|
1054
|
+
let tsEstree;
|
|
1055
|
+
try {
|
|
1056
|
+
tsEstree = await import('@typescript-eslint/typescript-estree');
|
|
1057
|
+
} catch {
|
|
1058
|
+
return extractKeysFromScriptRegex(source);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const ast = tsEstree.parse(source, {
|
|
1062
|
+
range: true,
|
|
1063
|
+
jsx: true,
|
|
1064
|
+
allowInvalidAST: true,
|
|
1065
|
+
suppressDeprecatedPropertyWarnings: true
|
|
1066
|
+
});
|
|
1067
|
+
tsEstree.simpleTraverse(ast, {
|
|
1068
|
+
enter(node) {
|
|
1069
|
+
if (node.type !== tsEstree.AST_NODE_TYPES.CallExpression) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const calleeName = getCalleeName(node, tsEstree.AST_NODE_TYPES);
|
|
1073
|
+
if (!calleeName || !I18N_FUNCTIONS.has(calleeName)) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const firstArg = node.arguments[0];
|
|
1077
|
+
if (!firstArg) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (firstArg.type === tsEstree.AST_NODE_TYPES.Literal && typeof firstArg.value === "string") {
|
|
1081
|
+
keys.push(firstArg.value);
|
|
1082
|
+
} else if (firstArg.type === tsEstree.AST_NODE_TYPES.TemplateLiteral && firstArg.expressions.length === 0 && firstArg.quasis.length === 1) {
|
|
1083
|
+
const value = firstArg.quasis[0].value.cooked;
|
|
1084
|
+
if (value) {
|
|
1085
|
+
keys.push(value);
|
|
1086
|
+
}
|
|
1087
|
+
} else {
|
|
1088
|
+
hasDynamic = true;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
} catch {
|
|
1093
|
+
return extractKeysFromScriptRegex(source);
|
|
1094
|
+
}
|
|
1095
|
+
return { keys, hasDynamic };
|
|
1096
|
+
}
|
|
1097
|
+
function getCalleeName(node, AST_NODE_TYPES) {
|
|
1098
|
+
const callee = node.callee;
|
|
1099
|
+
if (callee.type === AST_NODE_TYPES.Identifier) {
|
|
1100
|
+
return callee.name;
|
|
1101
|
+
}
|
|
1102
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier) {
|
|
1103
|
+
return callee.property.name;
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function extractKeysFromScriptRegex(source) {
|
|
1108
|
+
const keys = [];
|
|
1109
|
+
let match;
|
|
1110
|
+
const regex = /\b(?:\$?t|tc|\$tc|te|\$te|d|\$d|n|\$n)\s*\(\s*['"]([^'"]+)['"]/g;
|
|
1111
|
+
while ((match = regex.exec(source)) !== null) {
|
|
1112
|
+
keys.push(match[1]);
|
|
1113
|
+
}
|
|
1114
|
+
return { keys, hasDynamic: false };
|
|
1115
|
+
}
|
|
1116
|
+
function extractKeysFromTemplate(templateContent) {
|
|
1117
|
+
const keys = [];
|
|
1118
|
+
let hasDynamic = false;
|
|
1119
|
+
let match;
|
|
1120
|
+
TEMPLATE_T_REGEX.lastIndex = 0;
|
|
1121
|
+
VT_DIRECTIVE_STRING_REGEX.lastIndex = 0;
|
|
1122
|
+
VT_DIRECTIVE_PATH_REGEX.lastIndex = 0;
|
|
1123
|
+
TEMPLATE_DYNAMIC_T_REGEX.lastIndex = 0;
|
|
1124
|
+
while ((match = TEMPLATE_T_REGEX.exec(templateContent)) !== null) {
|
|
1125
|
+
keys.push(match[1]);
|
|
1126
|
+
}
|
|
1127
|
+
while ((match = VT_DIRECTIVE_STRING_REGEX.exec(templateContent)) !== null) {
|
|
1128
|
+
keys.push(match[1]);
|
|
1129
|
+
}
|
|
1130
|
+
while ((match = VT_DIRECTIVE_PATH_REGEX.exec(templateContent)) !== null) {
|
|
1131
|
+
keys.push(match[1]);
|
|
1132
|
+
}
|
|
1133
|
+
if (TEMPLATE_DYNAMIC_T_REGEX.test(templateContent)) {
|
|
1134
|
+
hasDynamic = true;
|
|
1135
|
+
}
|
|
1136
|
+
return { keys, hasDynamic };
|
|
1137
|
+
}
|
|
1138
|
+
async function analyzeVueSFC(content) {
|
|
1139
|
+
const keys = [];
|
|
1140
|
+
let hasDynamic = false;
|
|
1141
|
+
let parse;
|
|
1142
|
+
try {
|
|
1143
|
+
const compilerSfc = await import('vue/compiler-sfc');
|
|
1144
|
+
parse = compilerSfc.parse;
|
|
1145
|
+
} catch {
|
|
1146
|
+
const templateResult = extractKeysFromTemplate(content);
|
|
1147
|
+
const scriptResult = await extractKeysFromScript(content);
|
|
1148
|
+
return {
|
|
1149
|
+
keys: [...templateResult.keys, ...scriptResult.keys],
|
|
1150
|
+
hasDynamic: templateResult.hasDynamic || scriptResult.hasDynamic
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const { descriptor } = parse(content);
|
|
1154
|
+
if (descriptor.template?.content) {
|
|
1155
|
+
const templateResult = extractKeysFromTemplate(descriptor.template.content);
|
|
1156
|
+
keys.push(...templateResult.keys);
|
|
1157
|
+
if (templateResult.hasDynamic) {
|
|
1158
|
+
hasDynamic = true;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
const scriptContent = descriptor.scriptSetup?.content || descriptor.script?.content;
|
|
1162
|
+
if (scriptContent) {
|
|
1163
|
+
const scriptResult = await extractKeysFromScript(scriptContent);
|
|
1164
|
+
keys.push(...scriptResult.keys);
|
|
1165
|
+
if (scriptResult.hasDynamic) {
|
|
1166
|
+
hasDynamic = true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return { keys, hasDynamic };
|
|
1170
|
+
}
|
|
1171
|
+
async function analyzeFile(content, filePath, collector) {
|
|
1172
|
+
let result;
|
|
1173
|
+
if (filePath.endsWith(".vue")) {
|
|
1174
|
+
result = await analyzeVueSFC(content);
|
|
1175
|
+
} else {
|
|
1176
|
+
result = await extractKeysFromScript(content);
|
|
1177
|
+
}
|
|
1178
|
+
for (const key of result.keys) {
|
|
1179
|
+
collector.addKey(key);
|
|
1180
|
+
}
|
|
1181
|
+
if (result.hasDynamic) {
|
|
1182
|
+
collector.markDynamic();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const debug$1 = createDebug__default(resolveNamespace("tree-shaking"));
|
|
1187
|
+
function treeShakingPlugin(resolvedOptions, collector) {
|
|
1188
|
+
let projectRoot = "";
|
|
1189
|
+
return {
|
|
1190
|
+
name: resolveNamespace("tree-shaking"),
|
|
1191
|
+
enforce: "pre",
|
|
1192
|
+
vite: {
|
|
1193
|
+
configResolved(config) {
|
|
1194
|
+
projectRoot = config.root;
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
webpack(compiler) {
|
|
1198
|
+
projectRoot = compiler.options.context || process.cwd();
|
|
1199
|
+
},
|
|
1200
|
+
async buildStart() {
|
|
1201
|
+
const treeShaking = resolvedOptions.treeShaking;
|
|
1202
|
+
const patterns = treeShaking?.scanPatterns || [`${projectRoot}/src/**/*.{vue,ts,js,tsx,jsx}`];
|
|
1203
|
+
debug$1("scanning patterns:", patterns);
|
|
1204
|
+
const files = await fg__default(patterns, {
|
|
1205
|
+
ignore: ["**/node_modules/**"],
|
|
1206
|
+
absolute: true
|
|
1207
|
+
});
|
|
1208
|
+
debug$1(`found ${files.length} source files to scan`);
|
|
1209
|
+
for (const file of files) {
|
|
1210
|
+
try {
|
|
1211
|
+
const content = await node_fs.promises.readFile(file, "utf-8");
|
|
1212
|
+
await analyzeFile(content, file, collector);
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
debug$1(`failed to analyze file: ${file}`, err);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
debug$1(
|
|
1218
|
+
`scan complete: ${collector.usedKeys.size} used keys found, dynamic: ${collector.dynamicKeysDetected}`
|
|
1219
|
+
);
|
|
1220
|
+
if (collector.dynamicKeysDetected) {
|
|
1221
|
+
const strategy = treeShaking?.dynamicKeyStrategy || "keep-all";
|
|
1222
|
+
if (strategy === "keep-all") {
|
|
1223
|
+
warn(
|
|
1224
|
+
"Tree-shaking: dynamic key usage detected. All keys will be preserved (dynamicKeyStrategy: keep-all)."
|
|
1225
|
+
);
|
|
1226
|
+
} else {
|
|
1227
|
+
warn(
|
|
1228
|
+
"Tree-shaking: dynamic key usage detected. Unused keys will still be removed (dynamicKeyStrategy: ignore)."
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
buildEnd() {
|
|
1234
|
+
const diagnostics = collector.getDiagnostics();
|
|
1235
|
+
if (diagnostics.totalRemoved > 0) {
|
|
1236
|
+
debug$1(`tree-shaking: removed ${diagnostics.totalRemoved} unused message keys`);
|
|
1237
|
+
for (const [file, keys] of diagnostics.byFile) {
|
|
1238
|
+
debug$1(` ${file}: removed ${keys.length} keys (${keys.join(", ")})`);
|
|
1239
|
+
}
|
|
1240
|
+
} else {
|
|
1241
|
+
debug$1("tree-shaking: no keys removed");
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
969
1247
|
const debug = createDebug__default(resolveNamespace("root"));
|
|
970
1248
|
const unpluginFactory = (options = {}, meta) => {
|
|
971
1249
|
debug("meta framework", meta.framework);
|
|
@@ -975,7 +1253,12 @@ const unpluginFactory = (options = {}, meta) => {
|
|
|
975
1253
|
debug("plugin options (resolving):", options);
|
|
976
1254
|
const resolvedOptions = resolveOptions(options);
|
|
977
1255
|
debug("plugin options (resolved):", resolvedOptions);
|
|
978
|
-
const
|
|
1256
|
+
const collector = resolvedOptions.treeShaking ? createUsedKeysCollector(resolvedOptions.treeShaking) : null;
|
|
1257
|
+
const plugins = [];
|
|
1258
|
+
if (resolvedOptions.treeShaking && collector) {
|
|
1259
|
+
plugins.push(treeShakingPlugin(resolvedOptions, collector));
|
|
1260
|
+
}
|
|
1261
|
+
plugins.push(resourcePlugin(resolvedOptions, meta, collector));
|
|
979
1262
|
if (resolvedOptions.optimizeTranslationDirective) {
|
|
980
1263
|
if (meta.framework === "webpack") {
|
|
981
1264
|
raiseError(
|
package/lib/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UnpluginInstance, UnpluginFactory } from 'unplugin';
|
|
2
2
|
import { PluginOptions } from './types.cjs';
|
|
3
|
-
export { SFCLangFormat, VueI18nModule } from './types.cjs';
|
|
3
|
+
export { SFCLangFormat, TreeShakingOptions, VueI18nModule } from './types.cjs';
|
|
4
4
|
|
|
5
5
|
declare const unpluginFactory: UnpluginFactory<PluginOptions | undefined>;
|
|
6
6
|
declare const unplugin: UnpluginInstance<PluginOptions | undefined, boolean>;
|
package/lib/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UnpluginInstance, UnpluginFactory } from 'unplugin';
|
|
2
2
|
import { PluginOptions } from './types.mjs';
|
|
3
|
-
export { SFCLangFormat, VueI18nModule } from './types.mjs';
|
|
3
|
+
export { SFCLangFormat, TreeShakingOptions, VueI18nModule } from './types.mjs';
|
|
4
4
|
|
|
5
5
|
declare const unpluginFactory: UnpluginFactory<PluginOptions | undefined>;
|
|
6
6
|
declare const unplugin: UnpluginInstance<PluginOptions | undefined, boolean>;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UnpluginInstance, UnpluginFactory } from 'unplugin';
|
|
2
2
|
import { PluginOptions } from './types.js';
|
|
3
|
-
export { SFCLangFormat, VueI18nModule } from './types.js';
|
|
3
|
+
export { SFCLangFormat, TreeShakingOptions, VueI18nModule } from './types.js';
|
|
4
4
|
|
|
5
5
|
declare const unpluginFactory: UnpluginFactory<PluginOptions | undefined>;
|
|
6
6
|
declare const unplugin: UnpluginInstance<PluginOptions | undefined, boolean>;
|