@intlayer/babel 8.6.10 → 8.7.0-canary.1
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/dist/cjs/babel-plugin-intlayer-extract.cjs +1 -1
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +172 -0
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +1 -1
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +171 -0
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -0
- package/dist/cjs/extractContent/contentWriter.cjs +1 -1
- package/dist/cjs/extractContent/extractContent.cjs +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs +1 -1
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs +1 -1
- package/dist/cjs/extractScriptBlocks.cjs +80 -0
- package/dist/cjs/extractScriptBlocks.cjs.map +1 -0
- package/dist/cjs/getOptimizePluginOptions.cjs +1 -1
- package/dist/cjs/index.cjs +19 -0
- package/dist/cjs/transformers.cjs +150 -0
- package/dist/cjs/transformers.cjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-extract.mjs +1 -1
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +169 -0
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +1 -1
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +167 -0
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -0
- package/dist/esm/extractContent/contentWriter.mjs +1 -1
- package/dist/esm/extractContent/extractContent.mjs +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs +1 -1
- package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs +1 -1
- package/dist/esm/extractScriptBlocks.mjs +77 -0
- package/dist/esm/extractScriptBlocks.mjs.map +1 -0
- package/dist/esm/getOptimizePluginOptions.mjs +1 -1
- package/dist/esm/index.mjs +5 -1
- package/dist/esm/transformers.mjs +142 -0
- package/dist/esm/transformers.mjs.map +1 -0
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts +57 -0
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +119 -0
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -0
- package/dist/types/extractScriptBlocks.d.ts +45 -0
- package/dist/types/extractScriptBlocks.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/transformers.d.ts +64 -0
- package/dist/types/transformers.d.ts.map +1 -0
- package/package.json +10 -10
|
@@ -2,10 +2,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
3
|
const require_extractContent_utils_detectPackageName = require('./extractContent/utils/detectPackageName.cjs');
|
|
4
4
|
const require_extractContent_extractContent = require('./extractContent/extractContent.cjs');
|
|
5
|
+
let node_path = require("node:path");
|
|
5
6
|
let _intlayer_config_colors = require("@intlayer/config/colors");
|
|
6
7
|
_intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
|
|
7
8
|
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
8
|
-
let node_path = require("node:path");
|
|
9
9
|
let _babel_parser = require("@babel/parser");
|
|
10
10
|
|
|
11
11
|
//#region src/babel-plugin-intlayer-extract.ts
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_babel_plugin_intlayer_usage_analyzer = require('./babel-plugin-intlayer-usage-analyzer.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/babel-plugin-intlayer-field-rename.ts
|
|
5
|
+
/**
|
|
6
|
+
* Intlayer internal property names that must never be used as short-name
|
|
7
|
+
* targets. These appear inside content-node values (e.g. `{ nodeType:
|
|
8
|
+
* "translation", … }`) and are read by the intlayer runtime.
|
|
9
|
+
*/
|
|
10
|
+
const RESERVED_CONTENT_FIELD_NAMES = new Set(["nodeType"]);
|
|
11
|
+
/**
|
|
12
|
+
* Converts a zero-based index to a short alphabetic identifier.
|
|
13
|
+
* 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …
|
|
14
|
+
*/
|
|
15
|
+
const generateShortFieldName = (index) => {
|
|
16
|
+
const ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
17
|
+
const remainder = index % 26;
|
|
18
|
+
const quotient = Math.floor(index / 26);
|
|
19
|
+
return quotient === 0 ? ALPHABET[remainder] : generateShortFieldName(quotient - 1) + ALPHABET[remainder];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Recursively builds a `NestedRenameMap` from a compiled dictionary content
|
|
23
|
+
* value by traversing the intlayer node structure.
|
|
24
|
+
*
|
|
25
|
+
* Rules:
|
|
26
|
+
* - If the value has `nodeType: 'translation'`, user-defined fields live
|
|
27
|
+
* inside `translation[locale]`. Recurse into the first locale's value.
|
|
28
|
+
* - All other intlayer runtime nodes (enumeration, condition, gender, …) are
|
|
29
|
+
* treated as leaves — their internal keys must never be renamed.
|
|
30
|
+
* - Plain objects are user-defined records: rename their keys (filtered by
|
|
31
|
+
* `usedFieldFilter` at the top level) and recurse into each value.
|
|
32
|
+
* - Primitives and arrays produce an empty map (no further renaming).
|
|
33
|
+
*
|
|
34
|
+
* @param contentValue - The dictionary content value to analyse.
|
|
35
|
+
* @param usedFieldFilter - When provided, only keys in this set are included
|
|
36
|
+
* at the current level (matches the usage-analysis
|
|
37
|
+
* results so we don't rename purged fields).
|
|
38
|
+
*/
|
|
39
|
+
const buildNestedRenameMapFromContent = (contentValue, usedFieldFilter) => {
|
|
40
|
+
if (!contentValue || typeof contentValue !== "object" || Array.isArray(contentValue)) return /* @__PURE__ */ new Map();
|
|
41
|
+
const record = contentValue;
|
|
42
|
+
if (typeof record.nodeType === "string") {
|
|
43
|
+
if (record.translation && typeof record.translation === "object" && !Array.isArray(record.translation)) {
|
|
44
|
+
const firstLocaleValue = Object.values(record.translation)[0];
|
|
45
|
+
return buildNestedRenameMapFromContent(firstLocaleValue, usedFieldFilter);
|
|
46
|
+
}
|
|
47
|
+
return /* @__PURE__ */ new Map();
|
|
48
|
+
}
|
|
49
|
+
const allKeys = Object.keys(record).filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key));
|
|
50
|
+
const sortedKeys = [...usedFieldFilter ? allKeys.filter((key) => usedFieldFilter.has(key)) : allKeys].sort();
|
|
51
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
52
|
+
for (let i = 0; i < sortedKeys.length; i++) {
|
|
53
|
+
const key = sortedKeys[i];
|
|
54
|
+
const children = buildNestedRenameMapFromContent(record[key]);
|
|
55
|
+
renameMap.set(key, {
|
|
56
|
+
shortName: generateShortFieldName(i),
|
|
57
|
+
children
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return renameMap;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Walks a MemberExpression chain starting from `startPath`, renaming each
|
|
64
|
+
* property found in `currentRenameMap` at the corresponding nesting level.
|
|
65
|
+
*/
|
|
66
|
+
const walkRenameChain = (babelTypes, startPath, currentRenameMap) => {
|
|
67
|
+
let refPath = startPath;
|
|
68
|
+
let renameMap = currentRenameMap;
|
|
69
|
+
while (renameMap.size > 0) {
|
|
70
|
+
const parentPath = refPath.parentPath;
|
|
71
|
+
if (!parentPath) break;
|
|
72
|
+
const parentNode = parentPath.node;
|
|
73
|
+
if (!babelTypes.isMemberExpression(parentNode) && !babelTypes.isOptionalMemberExpression(parentNode) || parentNode.object !== refPath.node) break;
|
|
74
|
+
const memberNode = parentNode;
|
|
75
|
+
let fieldName;
|
|
76
|
+
if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) fieldName = memberNode.property.name;
|
|
77
|
+
else if (memberNode.computed && babelTypes.isStringLiteral(memberNode.property)) fieldName = memberNode.property.value;
|
|
78
|
+
else break;
|
|
79
|
+
const renameEntry = renameMap.get(fieldName);
|
|
80
|
+
if (!renameEntry) break;
|
|
81
|
+
if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) memberNode.property.name = renameEntry.shortName;
|
|
82
|
+
else if (memberNode.computed && babelTypes.isStringLiteral(memberNode.property)) memberNode.property.value = renameEntry.shortName;
|
|
83
|
+
refPath = parentPath;
|
|
84
|
+
renameMap = renameEntry.children;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Creates a Babel plugin that rewrites dictionary content field accesses in
|
|
89
|
+
* source files to their short aliases defined in
|
|
90
|
+
* `pruneContext.dictionaryKeyToFieldRenameMap`.
|
|
91
|
+
*
|
|
92
|
+
* Handled patterns (mirrors the usage analyser):
|
|
93
|
+
*
|
|
94
|
+
* const { fieldA, fieldB } = useIntlayer('key')
|
|
95
|
+
* → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')
|
|
96
|
+
*
|
|
97
|
+
* useIntlayer('key').fieldA
|
|
98
|
+
* → useIntlayer('key').shortA
|
|
99
|
+
*
|
|
100
|
+
* const result = useIntlayer('key'); result.fieldA
|
|
101
|
+
* → const result = useIntlayer('key'); result.shortA
|
|
102
|
+
*
|
|
103
|
+
* This plugin must run in a separate `transformAsync` pass **before**
|
|
104
|
+
* `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`
|
|
105
|
+
* with `useDictionary`, erasing the dictionary-key information needed here.
|
|
106
|
+
*/
|
|
107
|
+
const makeFieldRenameBabelPlugin = (pruneContext) => ({ types: babelTypes }) => ({
|
|
108
|
+
name: "intlayer-field-rename",
|
|
109
|
+
visitor: { Program: { exit: (programPath) => {
|
|
110
|
+
if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;
|
|
111
|
+
const intlayerCallerLocalNameMap = /* @__PURE__ */ new Map();
|
|
112
|
+
programPath.traverse({ ImportDeclaration: (importDeclarationPath) => {
|
|
113
|
+
for (const importSpecifier of importDeclarationPath.node.specifiers) {
|
|
114
|
+
if (!babelTypes.isImportSpecifier(importSpecifier)) continue;
|
|
115
|
+
const importedName = babelTypes.isIdentifier(importSpecifier.imported) ? importSpecifier.imported.name : importSpecifier.imported.value;
|
|
116
|
+
if (require_babel_plugin_intlayer_usage_analyzer.INTLAYER_CALLER_NAMES.includes(importedName)) intlayerCallerLocalNameMap.set(importSpecifier.local.name, importedName);
|
|
117
|
+
}
|
|
118
|
+
} });
|
|
119
|
+
if (intlayerCallerLocalNameMap.size === 0) return;
|
|
120
|
+
programPath.traverse({ CallExpression: (callExpressionPath) => {
|
|
121
|
+
const calleeNode = callExpressionPath.node.callee;
|
|
122
|
+
let localCallerName;
|
|
123
|
+
if (babelTypes.isIdentifier(calleeNode)) localCallerName = calleeNode.name;
|
|
124
|
+
else if (babelTypes.isMemberExpression(calleeNode) && babelTypes.isIdentifier(calleeNode.property)) localCallerName = calleeNode.property.name;
|
|
125
|
+
if (!localCallerName || !intlayerCallerLocalNameMap.has(localCallerName)) return;
|
|
126
|
+
const callArguments = callExpressionPath.node.arguments;
|
|
127
|
+
if (callArguments.length === 0) return;
|
|
128
|
+
const firstArgument = callArguments[0];
|
|
129
|
+
let dictionaryKey;
|
|
130
|
+
if (babelTypes.isStringLiteral(firstArgument)) dictionaryKey = firstArgument.value;
|
|
131
|
+
else if (babelTypes.isTemplateLiteral(firstArgument) && firstArgument.expressions.length === 0 && firstArgument.quasis.length === 1) dictionaryKey = firstArgument.quasis[0].value.cooked ?? firstArgument.quasis[0].value.raw;
|
|
132
|
+
if (!dictionaryKey) return;
|
|
133
|
+
const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
|
|
134
|
+
if (!fieldRenameMap || fieldRenameMap.size === 0) return;
|
|
135
|
+
const parentNode = callExpressionPath.parent;
|
|
136
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
|
|
137
|
+
for (const property of parentNode.id.properties) {
|
|
138
|
+
if (!babelTypes.isObjectProperty(property)) continue;
|
|
139
|
+
const keyName = babelTypes.isIdentifier(property.key) ? property.key.name : babelTypes.isStringLiteral(property.key) ? property.key.value : null;
|
|
140
|
+
if (!keyName) continue;
|
|
141
|
+
const renameEntry = fieldRenameMap.get(keyName);
|
|
142
|
+
if (!renameEntry) continue;
|
|
143
|
+
if (property.shorthand) {
|
|
144
|
+
property.shorthand = false;
|
|
145
|
+
property.key = babelTypes.identifier(renameEntry.shortName);
|
|
146
|
+
} else property.key = babelTypes.identifier(renameEntry.shortName);
|
|
147
|
+
if (renameEntry.children.size > 0 && babelTypes.isIdentifier(property.value)) {
|
|
148
|
+
const localVarBinding = callExpressionPath.scope.getBinding(property.value.name);
|
|
149
|
+
if (localVarBinding) for (const refPath of localVarBinding.referencePaths) walkRenameChain(babelTypes, refPath, renameEntry.children);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === callExpressionPath.node) {
|
|
155
|
+
walkRenameChain(babelTypes, callExpressionPath, fieldRenameMap);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
|
|
159
|
+
const variableName = parentNode.id.name;
|
|
160
|
+
const variableBinding = callExpressionPath.scope.getBinding(variableName);
|
|
161
|
+
if (!variableBinding) return;
|
|
162
|
+
for (const variableReferencePath of variableBinding.referencePaths) walkRenameChain(babelTypes, variableReferencePath, fieldRenameMap);
|
|
163
|
+
}
|
|
164
|
+
} });
|
|
165
|
+
} } }
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
exports.buildNestedRenameMapFromContent = buildNestedRenameMapFromContent;
|
|
170
|
+
exports.generateShortFieldName = generateShortFieldName;
|
|
171
|
+
exports.makeFieldRenameBabelPlugin = makeFieldRenameBabelPlugin;
|
|
172
|
+
//# sourceMappingURL=babel-plugin-intlayer-field-rename.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-field-rename.cjs","names":["INTLAYER_CALLER_NAMES"],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"sourcesContent":["import type { NodePath, PluginObj } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\n\n// ── Field-name helpers ────────────────────────────────────────────────────────\n\n/**\n * Intlayer internal property names that must never be used as short-name\n * targets. These appear inside content-node values (e.g. `{ nodeType:\n * \"translation\", … }`) and are read by the intlayer runtime.\n */\nconst RESERVED_CONTENT_FIELD_NAMES = new Set(['nodeType']);\n\n/**\n * Converts a zero-based index to a short alphabetic identifier.\n * 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …\n */\nexport const generateShortFieldName = (index: number): string => {\n const ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n const remainder = index % ALPHABET.length;\n const quotient = Math.floor(index / ALPHABET.length);\n return quotient === 0\n ? ALPHABET[remainder]\n : generateShortFieldName(quotient - 1) + ALPHABET[remainder];\n};\n\n/**\n * Recursively builds a `NestedRenameMap` from a compiled dictionary content\n * value by traversing the intlayer node structure.\n *\n * Rules:\n * - If the value has `nodeType: 'translation'`, user-defined fields live\n * inside `translation[locale]`. Recurse into the first locale's value.\n * - All other intlayer runtime nodes (enumeration, condition, gender, …) are\n * treated as leaves — their internal keys must never be renamed.\n * - Plain objects are user-defined records: rename their keys (filtered by\n * `usedFieldFilter` at the top level) and recurse into each value.\n * - Primitives and arrays produce an empty map (no further renaming).\n *\n * @param contentValue - The dictionary content value to analyse.\n * @param usedFieldFilter - When provided, only keys in this set are included\n * at the current level (matches the usage-analysis\n * results so we don't rename purged fields).\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown,\n usedFieldFilter?: Set<string>\n): NestedRenameMap => {\n if (\n !contentValue ||\n typeof contentValue !== 'object' ||\n Array.isArray(contentValue)\n ) {\n return new Map();\n }\n\n const record = contentValue as Record<string, unknown>;\n\n // Any object with `nodeType: string` is an intlayer runtime node.\n if (typeof record.nodeType === 'string') {\n // Translation node: user-defined fields live inside translation[locale].\n if (\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const firstLocaleValue = Object.values(\n record.translation as Record<string, unknown>\n )[0];\n return buildNestedRenameMapFromContent(firstLocaleValue, usedFieldFilter);\n }\n // All other intlayer nodes have runtime-managed internal structure — return\n // an empty map so they are treated as leaves.\n return new Map();\n }\n\n // User-defined record: collect non-reserved keys\n const allKeys = Object.keys(record).filter(\n (key) => !RESERVED_CONTENT_FIELD_NAMES.has(key)\n );\n\n const keysToRename = usedFieldFilter\n ? allKeys.filter((key) => usedFieldFilter.has(key))\n : allKeys;\n\n const sortedKeys = [...keysToRename].sort();\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n const children = buildNestedRenameMapFromContent(record[key]); // no filter for nested\n renameMap.set(key, { shortName: generateShortFieldName(i), children });\n }\n\n return renameMap;\n};\n\n// ── Field-rename Babel plugin ─────────────────────────────────────────────────\n\n/**\n * Walks a MemberExpression chain starting from `startPath`, renaming each\n * property found in `currentRenameMap` at the corresponding nesting level.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): void => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n while (renameMap.size > 0) {\n const parentPath = refPath.parentPath;\n if (!parentPath) break;\n\n const parentNode = parentPath.node;\n\n if (\n (!babelTypes.isMemberExpression(parentNode) &&\n !babelTypes.isOptionalMemberExpression(parentNode)) ||\n (parentNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n break;\n }\n\n const memberNode = parentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n fieldName = memberNode.property.name;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n fieldName = memberNode.property.value;\n } else {\n break; // dynamic key – stop\n }\n\n const renameEntry = renameMap.get(fieldName);\n if (!renameEntry) break; // not in map – stop\n\n // Apply the rename\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n memberNode.property.name = renameEntry.shortName;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n memberNode.property.value = renameEntry.shortName;\n }\n\n refPath = parentPath;\n renameMap = renameEntry.children;\n }\n};\n\n/**\n * Creates a Babel plugin that rewrites dictionary content field accesses in\n * source files to their short aliases defined in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n *\n * Handled patterns (mirrors the usage analyser):\n *\n * const { fieldA, fieldB } = useIntlayer('key')\n * → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')\n *\n * useIntlayer('key').fieldA\n * → useIntlayer('key').shortA\n *\n * const result = useIntlayer('key'); result.fieldA\n * → const result = useIntlayer('key'); result.shortA\n *\n * This plugin must run in a separate `transformAsync` pass **before**\n * `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`\n * with `useDictionary`, erasing the dictionary-key information needed here.\n */\nexport const makeFieldRenameBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-field-rename',\n visitor: {\n Program: {\n exit: (programPath) => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;\n\n // Collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Visit all useIntlayer / getIntlayer call-sites and rename field accesses\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return;\n\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (!fieldRenameMap || fieldRenameMap.size === 0) return;\n\n const parentNode = callExpressionPath.parent;\n\n // ── Case 1: const { fieldA, fieldB } = useIntlayer('key') ────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const renameEntry = fieldRenameMap.get(keyName);\n if (!renameEntry) continue;\n\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n property.key = babelTypes.identifier(renameEntry.shortName);\n } else {\n property.key = babelTypes.identifier(renameEntry.shortName);\n }\n\n // Walk nested member accesses on the local variable\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarBinding = callExpressionPath.scope.getBinding(\n (property.value as BabelTypes.Identifier).name\n );\n if (localVarBinding) {\n for (const refPath of localVarBinding.referencePaths) {\n walkRenameChain(\n babelTypes,\n refPath,\n renameEntry.children\n );\n }\n }\n }\n }\n return;\n }\n\n // ── Case 2: useIntlayer('key').fieldA.nested ─────────────────────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n walkRenameChain(babelTypes, callExpressionPath, fieldRenameMap);\n return;\n }\n\n // ── Case 3: const result = useIntlayer('key'); result.fieldA ─────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding =\n callExpressionPath.scope.getBinding(variableName);\n if (!variableBinding) return;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n }\n }\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
|
-
let _intlayer_chokidar_utils = require("@intlayer/chokidar/utils");
|
|
4
3
|
let node_path = require("node:path");
|
|
4
|
+
let _intlayer_chokidar_utils = require("@intlayer/chokidar/utils");
|
|
5
5
|
let _intlayer_config_utils = require("@intlayer/config/utils");
|
|
6
6
|
|
|
7
7
|
//#region src/babel-plugin-intlayer-optimize.ts
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/babel-plugin-intlayer-usage-analyzer.ts
|
|
4
|
+
const createPruneContext = () => ({
|
|
5
|
+
dictionaryKeyToFieldUsageMap: /* @__PURE__ */ new Map(),
|
|
6
|
+
dictionariesWithEdgeCases: /* @__PURE__ */ new Set(),
|
|
7
|
+
hasUnparsableSourceFiles: false,
|
|
8
|
+
dictionaryKeysWithUntrackedBindings: /* @__PURE__ */ new Map(),
|
|
9
|
+
dictionaryKeyToFieldRenameMap: /* @__PURE__ */ new Map(),
|
|
10
|
+
dictionaryKeysWithOpaqueTopLevelFields: /* @__PURE__ */ new Map(),
|
|
11
|
+
dictionariesSkippingFieldRename: /* @__PURE__ */ new Set(),
|
|
12
|
+
pendingFrameworkAnalysis: /* @__PURE__ */ new Map()
|
|
13
|
+
});
|
|
14
|
+
/** Canonical intlayer caller names that trigger usage analysis. */
|
|
15
|
+
const INTLAYER_CALLER_NAMES = ["useIntlayer", "getIntlayer"];
|
|
16
|
+
/**
|
|
17
|
+
* Records the usage of a specific dictionary key's fields into `pruneContext`.
|
|
18
|
+
* Merges with any previously recorded usage for the same key.
|
|
19
|
+
*/
|
|
20
|
+
const recordFieldUsage = (pruneContext, dictionaryKey, fieldUsage) => {
|
|
21
|
+
const existingUsage = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
|
|
22
|
+
if (existingUsage === "all") return;
|
|
23
|
+
if (fieldUsage === "all") {
|
|
24
|
+
pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, "all");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const mergedFieldSet = existingUsage instanceof Set ? new Set([...existingUsage, ...fieldUsage]) : new Set(fieldUsage);
|
|
28
|
+
pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`
|
|
32
|
+
* call expression is consumed, then records the field usage into `pruneContext`.
|
|
33
|
+
*
|
|
34
|
+
* Recognised patterns:
|
|
35
|
+
* const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}
|
|
36
|
+
* useIntlayer('key').fieldA → records {fieldA}
|
|
37
|
+
* useIntlayer('key')['fieldA'] → records {fieldA}
|
|
38
|
+
* const { ...rest } = useIntlayer('key') → records 'all' (spread)
|
|
39
|
+
* const result = useIntlayer('key') → records 'all' (untracked binding)
|
|
40
|
+
*/
|
|
41
|
+
const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
|
|
42
|
+
const parentNode = callExpressionPath.parent;
|
|
43
|
+
/** Mark the dictionary key as having an untracked binding in this file. */
|
|
44
|
+
const markUntrackedBinding = () => {
|
|
45
|
+
const existingPaths = pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];
|
|
46
|
+
if (!existingPaths.includes(currentSourceFilePath)) pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [...existingPaths, currentSourceFilePath]);
|
|
47
|
+
recordFieldUsage(pruneContext, dictionaryKey, "all");
|
|
48
|
+
};
|
|
49
|
+
/** Record that a field value is consumed opaquely (not further destructured). */
|
|
50
|
+
const markOpaqueField = (fieldName, line) => {
|
|
51
|
+
const fieldToLocations = pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ?? /* @__PURE__ */ new Map();
|
|
52
|
+
const location = line !== void 0 ? `${currentSourceFilePath}:${line}` : currentSourceFilePath;
|
|
53
|
+
const locations = fieldToLocations.get(fieldName) ?? [];
|
|
54
|
+
if (!locations.includes(location)) locations.push(location);
|
|
55
|
+
fieldToLocations.set(fieldName, locations);
|
|
56
|
+
pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(dictionaryKey, fieldToLocations);
|
|
57
|
+
};
|
|
58
|
+
/** Register a plain variable binding in an SFC file for a second-pass analysis. */
|
|
59
|
+
const deferFrameworkAnalysis = (variableName) => {
|
|
60
|
+
const existing = pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];
|
|
61
|
+
if (!existing.some((e) => e.variableName === variableName && e.dictionaryKey === dictionaryKey)) existing.push({
|
|
62
|
+
variableName,
|
|
63
|
+
dictionaryKey
|
|
64
|
+
});
|
|
65
|
+
pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);
|
|
66
|
+
};
|
|
67
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
|
|
68
|
+
if (parentNode.id.properties.some((prop) => babelTypes.isRestElement(prop))) {
|
|
69
|
+
recordFieldUsage(pruneContext, dictionaryKey, "all");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const accessedFieldNames = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const property of parentNode.id.properties) if (babelTypes.isObjectProperty(property) && babelTypes.isIdentifier(property.key)) accessedFieldNames.add(property.key.name);
|
|
74
|
+
else if (babelTypes.isObjectProperty(property) && babelTypes.isStringLiteral(property.key)) accessedFieldNames.add(property.key.value);
|
|
75
|
+
recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === callExpressionPath.node) {
|
|
79
|
+
if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) recordFieldUsage(pruneContext, dictionaryKey, new Set([parentNode.property.name]));
|
|
80
|
+
else if (parentNode.computed && babelTypes.isStringLiteral(parentNode.property)) recordFieldUsage(pruneContext, dictionaryKey, new Set([parentNode.property.value]));
|
|
81
|
+
else markUntrackedBinding();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
|
|
85
|
+
const variableName = parentNode.id.name;
|
|
86
|
+
const variableBinding = callExpressionPath.scope.getBinding(variableName);
|
|
87
|
+
if (!variableBinding) {
|
|
88
|
+
markUntrackedBinding();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
|
|
92
|
+
let hasUntrackedReferenceAccess = false;
|
|
93
|
+
for (const variableReferencePath of variableBinding.referencePaths) {
|
|
94
|
+
const referenceParentNode = variableReferencePath.parent;
|
|
95
|
+
if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
|
|
96
|
+
const memberExpressionNode = referenceParentNode;
|
|
97
|
+
if (!memberExpressionNode.computed && babelTypes.isIdentifier(memberExpressionNode.property)) {
|
|
98
|
+
const fieldName = memberExpressionNode.property.name;
|
|
99
|
+
accessedTopLevelFieldNames.add(fieldName);
|
|
100
|
+
const memberExprPath = variableReferencePath.parentPath;
|
|
101
|
+
const grandParentNode = memberExprPath?.parent;
|
|
102
|
+
if (!((babelTypes.isMemberExpression(grandParentNode) || babelTypes.isOptionalMemberExpression(grandParentNode)) && grandParentNode.object === memberExprPath?.node)) markOpaqueField(fieldName, memberExprPath?.node.loc?.start.line);
|
|
103
|
+
} else if (memberExpressionNode.computed && babelTypes.isStringLiteral(memberExpressionNode.property)) {
|
|
104
|
+
const fieldName = memberExpressionNode.property.value;
|
|
105
|
+
accessedTopLevelFieldNames.add(fieldName);
|
|
106
|
+
const memberExprPath = variableReferencePath.parentPath;
|
|
107
|
+
const grandParentNode = memberExprPath?.parent;
|
|
108
|
+
if (!((babelTypes.isMemberExpression(grandParentNode) || babelTypes.isOptionalMemberExpression(grandParentNode)) && grandParentNode.object === memberExprPath?.node)) markOpaqueField(fieldName, memberExprPath?.node.loc?.start.line);
|
|
109
|
+
} else {
|
|
110
|
+
hasUntrackedReferenceAccess = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
} else if (babelTypes.isArrayExpression(referenceParentNode)) {} else {
|
|
114
|
+
hasUntrackedReferenceAccess = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (hasUntrackedReferenceAccess) markUntrackedBinding();
|
|
119
|
+
else if (isSfcFile) deferFrameworkAnalysis(variableName);
|
|
120
|
+
else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
|
|
121
|
+
else recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (babelTypes.isExpressionStatement(parentNode)) return;
|
|
125
|
+
markUntrackedBinding();
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Creates a Babel plugin that traverses source files and records which
|
|
129
|
+
* top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site
|
|
130
|
+
* accesses. Results are accumulated into `pruneContext`.
|
|
131
|
+
*
|
|
132
|
+
* This plugin is analysis-only: it does not transform the code (`code: false`
|
|
133
|
+
* should be passed to `transformAsync` when using it).
|
|
134
|
+
*/
|
|
135
|
+
const makeUsageAnalyzerBabelPlugin = (pruneContext) => ({ types: babelTypes }) => ({
|
|
136
|
+
name: "intlayer-usage-analyzer",
|
|
137
|
+
visitor: { Program: { exit: (programPath, state) => {
|
|
138
|
+
const currentSourceFilePath = state.file.opts.filename ?? "unknown file";
|
|
139
|
+
const isSfcFile = currentSourceFilePath.endsWith(".vue") || currentSourceFilePath.endsWith(".svelte");
|
|
140
|
+
const intlayerCallerLocalNameMap = /* @__PURE__ */ new Map();
|
|
141
|
+
programPath.traverse({ ImportDeclaration: (importDeclarationPath) => {
|
|
142
|
+
for (const importSpecifier of importDeclarationPath.node.specifiers) {
|
|
143
|
+
if (!babelTypes.isImportSpecifier(importSpecifier)) continue;
|
|
144
|
+
const importedName = babelTypes.isIdentifier(importSpecifier.imported) ? importSpecifier.imported.name : importSpecifier.imported.value;
|
|
145
|
+
if (INTLAYER_CALLER_NAMES.includes(importedName)) intlayerCallerLocalNameMap.set(importSpecifier.local.name, importedName);
|
|
146
|
+
}
|
|
147
|
+
} });
|
|
148
|
+
if (intlayerCallerLocalNameMap.size === 0) return;
|
|
149
|
+
programPath.traverse({ CallExpression: (callExpressionPath) => {
|
|
150
|
+
const calleeNode = callExpressionPath.node.callee;
|
|
151
|
+
let localCallerName;
|
|
152
|
+
if (babelTypes.isIdentifier(calleeNode)) localCallerName = calleeNode.name;
|
|
153
|
+
else if (babelTypes.isMemberExpression(calleeNode) && babelTypes.isIdentifier(calleeNode.property)) localCallerName = calleeNode.property.name;
|
|
154
|
+
if (!localCallerName || !intlayerCallerLocalNameMap.has(localCallerName)) return;
|
|
155
|
+
const callArguments = callExpressionPath.node.arguments;
|
|
156
|
+
if (callArguments.length === 0) return;
|
|
157
|
+
const firstArgument = callArguments[0];
|
|
158
|
+
let dictionaryKey;
|
|
159
|
+
if (babelTypes.isStringLiteral(firstArgument)) dictionaryKey = firstArgument.value;
|
|
160
|
+
else if (babelTypes.isTemplateLiteral(firstArgument) && firstArgument.expressions.length === 0 && firstArgument.quasis.length === 1) dictionaryKey = firstArgument.quasis[0].value.cooked ?? firstArgument.quasis[0].value.raw;
|
|
161
|
+
if (!dictionaryKey) return;
|
|
162
|
+
analyzeCallExpressionUsage(babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile);
|
|
163
|
+
} });
|
|
164
|
+
} } }
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
exports.INTLAYER_CALLER_NAMES = INTLAYER_CALLER_NAMES;
|
|
169
|
+
exports.createPruneContext = createPruneContext;
|
|
170
|
+
exports.makeUsageAnalyzerBabelPlugin = makeUsageAnalyzerBabelPlugin;
|
|
171
|
+
//# sourceMappingURL=babel-plugin-intlayer-usage-analyzer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-usage-analyzer.cjs","names":[],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\n\n// ── PruneContext types ────────────────────────────────────────────────────────\n\n/**\n * Dictionary field usage result for a single dictionary key.\n *\n * 'all' → could not determine statically which fields are used;\n * keep every field (no pruning possible).\n * Set<string> → the exact top-level content field names that were accessed.\n */\nexport type DictionaryFieldUsage = Set<string> | 'all';\n\n/**\n * One node in the nested field-rename tree.\n *\n * shortName – the compact alias assigned to this field name.\n * children – rename table for the next level of user-defined keys inside\n * this field's value (empty when the value is a leaf / primitive).\n */\nexport type NestedRenameEntry = {\n shortName: string;\n children: NestedRenameMap;\n};\n\n/** A level of the field-rename tree, mapping original field names to entries. */\nexport type NestedRenameMap = Map<string, NestedRenameEntry>;\n\n/**\n * Shared mutable state created once by the vite plugin and passed by reference\n * to the usage-analyzer (writer) and the prune/minify plugins (readers).\n *\n * All mutations happen during the usage-analysis `buildStart` phase; readers\n * only access this state during the subsequent `transform` phase.\n */\nexport type PruneContext = {\n /**\n * Maps every dictionary key seen in source files to the set of top-level\n * content fields statically accessed, or `'all'` when the access pattern\n * could not be determined.\n */\n dictionaryKeyToFieldUsageMap: Map<string, DictionaryFieldUsage>;\n\n /**\n * Dictionary keys for which the prune/minify step must be skipped entirely\n * because an edge case was detected during analysis or structure recognition.\n */\n dictionariesWithEdgeCases: Set<string>;\n\n /**\n * True if at least one source file failed to parse during the analysis phase.\n * The prune plugin uses this flag conservatively: any dictionary key without\n * a usage entry might have been referenced by the unparsable file.\n */\n hasUnparsableSourceFiles: boolean;\n\n /**\n * Maps dictionary keys to the source file paths where the result of\n * `useIntlayer` / `getIntlayer` was assigned to a plain variable, making\n * static field analysis impossible.\n */\n dictionaryKeysWithUntrackedBindings: Map<string, string[]>;\n\n /**\n * Maps each dictionary key to a nested field-rename tree built after the\n * usage analysis phase (only populated when `build.minify` is active and\n * the field usage for that dictionary is a finite `Set<string>`).\n */\n dictionaryKeyToFieldRenameMap: Map<string, NestedRenameMap>;\n\n /**\n * Maps each dictionary key to a per-field list of source locations where\n * the field value is consumed \"opaquely\" (passed as-is to a child component\n * or function argument). When a field is opaque AND has nested user-defined\n * structure, its children must not be renamed.\n *\n * Structure: dictionaryKey → fieldName → [\"filePath:line\", …]\n */\n dictionaryKeysWithOpaqueTopLevelFields: Map<string, Map<string, string[]>>;\n\n /**\n * Dictionary keys for which field-key renaming must be skipped even if a\n * finite field-usage set was determined.\n *\n * Populated for dictionaries whose plain-variable bindings were resolved by\n * the framework-specific extractor (Vue / Svelte SFCs), because the Babel\n * rename plugin cannot update the source-code property accesses for those\n * indirect patterns (Vue `.value.field` / Svelte `$store.field`).\n *\n * Pruning and basic minification still apply; only field-key renaming is\n * suppressed.\n */\n dictionariesSkippingFieldRename: Set<string>;\n\n /**\n * Plain variable bindings that require a framework-specific secondary pass.\n *\n * Populated during the Babel analysis phase for `.vue` and `.svelte` source\n * files where direct field access is not visible to Babel scope analysis:\n * - Vue: `content.value.fieldName` – the `.value` ref-accessor is hidden\n * - Svelte: `$varName.fieldName` – the `$` prefix creates a new identifier\n *\n * Structure: filePath → [{variableName, dictionaryKey}, …]\n */\n pendingFrameworkAnalysis: Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >;\n};\n\nexport const createPruneContext = (): PruneContext => ({\n dictionaryKeyToFieldUsageMap: new Map(),\n dictionariesWithEdgeCases: new Set(),\n hasUnparsableSourceFiles: false,\n dictionaryKeysWithUntrackedBindings: new Map(),\n dictionaryKeyToFieldRenameMap: new Map(),\n dictionaryKeysWithOpaqueTopLevelFields: new Map<\n string,\n Map<string, string[]>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\n});\n\n// ── Usage-analyzer Babel plugin ───────────────────────────────────────────────\n\n/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n const mergedFieldSet =\n existingUsage instanceof Set\n ? new Set([...existingUsage, ...fieldUsage])\n : new Set(fieldUsage);\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n const parentNode = callExpressionPath.parent;\n\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that a field value is consumed opaquely (not further destructured). */\n const markOpaqueField = (\n fieldName: string,\n line: number | undefined\n ): void => {\n const fieldToLocations =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ??\n new Map<string, string[]>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const locations = fieldToLocations.get(fieldName) ?? [];\n if (!locations.includes(location)) locations.push(location);\n fieldToLocations.set(fieldName, locations);\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(\n dictionaryKey,\n fieldToLocations\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ──────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const hasRestElement = parentNode.id.properties.some((prop) =>\n babelTypes.isRestElement(prop)\n );\n\n if (hasRestElement) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n const accessedFieldNames = new Set<string>();\n for (const property of parentNode.id.properties) {\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key)\n ) {\n accessedFieldNames.add(property.key.name);\n } else if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isStringLiteral(property.key)\n ) {\n accessedFieldNames.add(property.key.value);\n }\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ──────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([parentNode.property.name])\n );\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([parentNode.property.value])\n );\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ─────────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = callExpressionPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n let hasUntrackedReferenceAccess = false;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n const fieldName = memberExpressionNode.property.name;\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check if the field value is consumed opaquely (not chained further)\n const memberExprPath = variableReferencePath.parentPath;\n const grandParentNode = memberExprPath?.parent;\n const isChainedFurther =\n (babelTypes.isMemberExpression(grandParentNode) ||\n babelTypes.isOptionalMemberExpression(grandParentNode)) &&\n (grandParentNode as BabelTypes.MemberExpression).object ===\n memberExprPath?.node;\n\n if (!isChainedFurther) {\n markOpaqueField(fieldName, memberExprPath?.node.loc?.start.line);\n }\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n const fieldName = memberExpressionNode.property.value;\n accessedTopLevelFieldNames.add(fieldName);\n\n const memberExprPath = variableReferencePath.parentPath;\n const grandParentNode = memberExprPath?.parent;\n const isChainedFurther =\n (babelTypes.isMemberExpression(grandParentNode) ||\n babelTypes.isOptionalMemberExpression(grandParentNode)) &&\n (grandParentNode as BabelTypes.MemberExpression).object ===\n memberExprPath?.node;\n\n if (!isChainedFurther) {\n markOpaqueField(fieldName, memberExprPath?.node.loc?.start.line);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // Ignore array literals (e.g. [content]) – uncommon but benign\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n hasUntrackedReferenceAccess = true;\n break;\n }\n }\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – conservatively keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ─────────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ─────────────\n markUntrackedBinding();\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site\n * accesses. Results are accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte');\n\n // Phase 1: collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return; // dynamic key – cannot resolve which dictionary\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
3
|
const require_extractContent_utils_extractDictionaryInfo = require('./utils/extractDictionaryInfo.cjs');
|
|
4
|
+
let node_path = require("node:path");
|
|
4
5
|
let node_fs = require("node:fs");
|
|
5
6
|
let node_fs_promises = require("node:fs/promises");
|
|
6
|
-
let node_path = require("node:path");
|
|
7
7
|
let _intlayer_chokidar_build = require("@intlayer/chokidar/build");
|
|
8
8
|
let _intlayer_core_plugins = require("@intlayer/core/plugins");
|
|
9
9
|
let _intlayer_types_nodeType = require("@intlayer/types/nodeType");
|
|
@@ -7,12 +7,12 @@ const require_extractContent_utils_generateKey = require('./utils/generateKey.cj
|
|
|
7
7
|
const require_extractContent_utils_shouldExtract = require('./utils/shouldExtract.cjs');
|
|
8
8
|
const require_extractContent_babelProcessor = require('./babelProcessor.cjs');
|
|
9
9
|
const require_extractContent_processTsxFile = require('./processTsxFile.cjs');
|
|
10
|
+
let node_path = require("node:path");
|
|
10
11
|
let _intlayer_config_colors = require("@intlayer/config/colors");
|
|
11
12
|
_intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
|
|
12
13
|
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
13
14
|
let _intlayer_config_node = require("@intlayer/config/node");
|
|
14
15
|
let node_fs = require("node:fs");
|
|
15
|
-
let node_path = require("node:path");
|
|
16
16
|
let _intlayer_config_utils = require("@intlayer/config/utils");
|
|
17
17
|
let _intlayer_unmerged_dictionaries_entry = require("@intlayer/unmerged-dictionaries-entry");
|
|
18
18
|
let _intlayer_chokidar_cli = require("@intlayer/chokidar/cli");
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
3
3
|
const require_extractContent_utils_extractDictionaryKey = require('./extractDictionaryKey.cjs');
|
|
4
|
+
let node_path = require("node:path");
|
|
4
5
|
let _intlayer_chokidar_utils = require("@intlayer/chokidar/utils");
|
|
5
6
|
let _intlayer_config_colors = require("@intlayer/config/colors");
|
|
6
7
|
_intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
|
|
7
8
|
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
8
|
-
let node_path = require("node:path");
|
|
9
9
|
let _intlayer_config_utils = require("@intlayer/config/utils");
|
|
10
10
|
let _intlayer_unmerged_dictionaries_entry = require("@intlayer/unmerged-dictionaries-entry");
|
|
11
11
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
3
|
-
let node_fs = require("node:fs");
|
|
4
3
|
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
5
|
let _intlayer_unmerged_dictionaries_entry = require("@intlayer/unmerged-dictionaries-entry");
|
|
6
6
|
|
|
7
7
|
//#region src/extractContent/utils/resolveDictionaryKey.ts
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
|
|
5
|
+
//#region src/extractScriptBlocks.ts
|
|
6
|
+
/**
|
|
7
|
+
* Regex that matches every `<script …>…<\/script>` block in an SFC (Vue or
|
|
8
|
+
* Svelte). Works for both instance scripts and module/setup scripts.
|
|
9
|
+
*
|
|
10
|
+
* Limitations (shared with `@intlayer/svelte-compiler`'s own approach):
|
|
11
|
+
* - A literal `<\/script>` inside a string or comment inside the script block
|
|
12
|
+
* would prematurely close the match. This is an accepted trade-off for the
|
|
13
|
+
* vast majority of real-world files.
|
|
14
|
+
*/
|
|
15
|
+
const SFC_SCRIPT_RE = /<script([^>]*)>([\s\S]*?)<\/script>/g;
|
|
16
|
+
/**
|
|
17
|
+
* Extracts all `<script>` blocks from a Vue SFC or Svelte source string,
|
|
18
|
+
* returning each block's text content together with its start/end byte offsets
|
|
19
|
+
* in the original source.
|
|
20
|
+
*
|
|
21
|
+
* Uses the same regex strategy as `@intlayer/svelte-compiler` internally.
|
|
22
|
+
*/
|
|
23
|
+
const extractSFCScriptBlocks = (code) => {
|
|
24
|
+
const blocks = [];
|
|
25
|
+
SFC_SCRIPT_RE.lastIndex = 0;
|
|
26
|
+
for (let match = SFC_SCRIPT_RE.exec(code); match !== null; match = SFC_SCRIPT_RE.exec(code)) {
|
|
27
|
+
const openingTagLength = 7 + match[1].length + 1;
|
|
28
|
+
const contentStart = match.index + openingTagLength;
|
|
29
|
+
const content = match[2];
|
|
30
|
+
blocks.push({
|
|
31
|
+
content,
|
|
32
|
+
contentStartOffset: contentStart,
|
|
33
|
+
contentEndOffset: contentStart + content.length
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return blocks;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Extracts the script block(s) from a source file, dispatching by extension:
|
|
40
|
+
*
|
|
41
|
+
* - `.vue` / `.svelte` → searches for `<script>` blocks using a regex
|
|
42
|
+
* (same approach used by `@intlayer/svelte-compiler`). Returns one entry
|
|
43
|
+
* per block found (instance script + module/setup script).
|
|
44
|
+
* Returns an **empty array** when no `<script>` tag is found, which
|
|
45
|
+
* happens both for template-only SFCs and for already-compiled JS that
|
|
46
|
+
* Vite passes to `enforce:'post'` transform hooks.
|
|
47
|
+
* - everything else → treats the whole file as a single script block and
|
|
48
|
+
* returns it wrapped in a single-element array.
|
|
49
|
+
*/
|
|
50
|
+
const extractScriptBlocks = (filePath, code) => {
|
|
51
|
+
const ext = (0, node_path.extname)(filePath);
|
|
52
|
+
if (ext === ".vue" || ext === ".svelte") return extractSFCScriptBlocks(code);
|
|
53
|
+
return [{
|
|
54
|
+
content: code,
|
|
55
|
+
contentStartOffset: 0,
|
|
56
|
+
contentEndOffset: code.length
|
|
57
|
+
}];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Applies modified script block content back into the original source string.
|
|
61
|
+
*
|
|
62
|
+
* Each entry in `modifications` pairs an original `ScriptBlock` (as returned
|
|
63
|
+
* by `extractScriptBlocks`) with the replacement text for its content.
|
|
64
|
+
* Replacements are applied in reverse offset order so that earlier offsets
|
|
65
|
+
* remain valid while later replacements are being processed.
|
|
66
|
+
*
|
|
67
|
+
* Returns `originalCode` unchanged when `modifications` is empty.
|
|
68
|
+
*/
|
|
69
|
+
const injectScriptBlocks = (originalCode, modifications) => {
|
|
70
|
+
if (modifications.length === 0) return originalCode;
|
|
71
|
+
const sorted = [...modifications].sort((a, b) => b.block.contentStartOffset - a.block.contentStartOffset);
|
|
72
|
+
let result = originalCode;
|
|
73
|
+
for (const { block, modifiedContent } of sorted) result = result.slice(0, block.contentStartOffset) + modifiedContent + result.slice(block.contentEndOffset);
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
exports.extractScriptBlocks = extractScriptBlocks;
|
|
79
|
+
exports.injectScriptBlocks = injectScriptBlocks;
|
|
80
|
+
//# sourceMappingURL=extractScriptBlocks.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractScriptBlocks.cjs","names":[],"sources":["../../src/extractScriptBlocks.ts"],"sourcesContent":["import { extname } from 'node:path';\n\n/**\n * A script block extracted from an SFC (Vue or Svelte) source file.\n *\n * `content` – The raw JS/TS text between the opening and closing\n * `<script>` tags (does NOT include the tags).\n * `contentStartOffset` – Byte offset of `content[0]` in the full source string.\n * `contentEndOffset` – Byte offset one past the last byte of `content` in the\n * full source string (i.e. `source.slice(start, end) === content`).\n */\nexport type ScriptBlock = {\n content: string;\n contentStartOffset: number;\n contentEndOffset: number;\n};\n\n// ── SFC script extraction ─────────────────────────────────────────────────────\n\n/**\n * Regex that matches every `<script …>…</script>` block in an SFC (Vue or\n * Svelte). Works for both instance scripts and module/setup scripts.\n *\n * Limitations (shared with `@intlayer/svelte-compiler`'s own approach):\n * - A literal `</script>` inside a string or comment inside the script block\n * would prematurely close the match. This is an accepted trade-off for the\n * vast majority of real-world files.\n */\nconst SFC_SCRIPT_RE = /<script([^>]*)>([\\s\\S]*?)<\\/script>/g;\n\n/**\n * Extracts all `<script>` blocks from a Vue SFC or Svelte source string,\n * returning each block's text content together with its start/end byte offsets\n * in the original source.\n *\n * Uses the same regex strategy as `@intlayer/svelte-compiler` internally.\n */\nconst extractSFCScriptBlocks = (code: string): ScriptBlock[] => {\n const blocks: ScriptBlock[] = [];\n SFC_SCRIPT_RE.lastIndex = 0;\n\n for (\n let match = SFC_SCRIPT_RE.exec(code);\n match !== null;\n match = SFC_SCRIPT_RE.exec(code)\n ) {\n // match[0]: full `<script ATTRS>CONTENT</script>`\n // match[1]: the attribute string (may be empty)\n // match[2]: the script content\n const openingTagLength = '<script'.length + match[1].length + '>'.length;\n const contentStart = match.index + openingTagLength;\n const content = match[2];\n blocks.push({\n content,\n contentStartOffset: contentStart,\n contentEndOffset: contentStart + content.length,\n });\n }\n\n return blocks;\n};\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Extracts the script block(s) from a source file, dispatching by extension:\n *\n * - `.vue` / `.svelte` → searches for `<script>` blocks using a regex\n * (same approach used by `@intlayer/svelte-compiler`). Returns one entry\n * per block found (instance script + module/setup script).\n * Returns an **empty array** when no `<script>` tag is found, which\n * happens both for template-only SFCs and for already-compiled JS that\n * Vite passes to `enforce:'post'` transform hooks.\n * - everything else → treats the whole file as a single script block and\n * returns it wrapped in a single-element array.\n */\nexport const extractScriptBlocks = (\n filePath: string,\n code: string\n): ScriptBlock[] => {\n const ext = extname(filePath);\n\n if (ext === '.vue' || ext === '.svelte') {\n return extractSFCScriptBlocks(code);\n }\n\n // Plain JS / TS / JSX / TSX / MJS / CJS – the whole file is the script.\n return [\n {\n content: code,\n contentStartOffset: 0,\n contentEndOffset: code.length,\n },\n ];\n};\n\n/**\n * Applies modified script block content back into the original source string.\n *\n * Each entry in `modifications` pairs an original `ScriptBlock` (as returned\n * by `extractScriptBlocks`) with the replacement text for its content.\n * Replacements are applied in reverse offset order so that earlier offsets\n * remain valid while later replacements are being processed.\n *\n * Returns `originalCode` unchanged when `modifications` is empty.\n */\nexport const injectScriptBlocks = (\n originalCode: string,\n modifications: ReadonlyArray<{\n block: ScriptBlock;\n modifiedContent: string;\n }>\n): string => {\n if (modifications.length === 0) return originalCode;\n\n const sorted = [...modifications].sort(\n (a, b) => b.block.contentStartOffset - a.block.contentStartOffset\n );\n\n let result = originalCode;\n for (const { block, modifiedContent } of sorted) {\n result =\n result.slice(0, block.contentStartOffset) +\n modifiedContent +\n result.slice(block.contentEndOffset);\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;;;;;;;AA4BA,MAAM,gBAAgB;;;;;;;;AAStB,MAAM,0BAA0B,SAAgC;CAC9D,MAAM,SAAwB,EAAE;AAChC,eAAc,YAAY;AAE1B,MACE,IAAI,QAAQ,cAAc,KAAK,KAAK,EACpC,UAAU,MACV,QAAQ,cAAc,KAAK,KAAK,EAChC;EAIA,MAAM,mBAAmB,IAAmB,MAAM,GAAG,SAAS;EAC9D,MAAM,eAAe,MAAM,QAAQ;EACnC,MAAM,UAAU,MAAM;AACtB,SAAO,KAAK;GACV;GACA,oBAAoB;GACpB,kBAAkB,eAAe,QAAQ;GAC1C,CAAC;;AAGJ,QAAO;;;;;;;;;;;;;;AAiBT,MAAa,uBACX,UACA,SACkB;CAClB,MAAM,6BAAc,SAAS;AAE7B,KAAI,QAAQ,UAAU,QAAQ,UAC5B,QAAO,uBAAuB,KAAK;AAIrC,QAAO,CACL;EACE,SAAS;EACT,oBAAoB;EACpB,kBAAkB,KAAK;EACxB,CACF;;;;;;;;;;;;AAaH,MAAa,sBACX,cACA,kBAIW;AACX,KAAI,cAAc,WAAW,EAAG,QAAO;CAEvC,MAAM,SAAS,CAAC,GAAG,cAAc,CAAC,MAC/B,GAAG,MAAM,EAAE,MAAM,qBAAqB,EAAE,MAAM,mBAChD;CAED,IAAI,SAAS;AACb,MAAK,MAAM,EAAE,OAAO,qBAAqB,OACvC,UACE,OAAO,MAAM,GAAG,MAAM,mBAAmB,GACzC,kBACA,OAAO,MAAM,MAAM,iBAAiB;AAGxC,QAAO"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
let node_path = require("node:path");
|
|
3
4
|
let _intlayer_chokidar_utils = require("@intlayer/chokidar/utils");
|
|
4
5
|
let _intlayer_config_node = require("@intlayer/config/node");
|
|
5
|
-
let node_path = require("node:path");
|
|
6
6
|
|
|
7
7
|
//#region src/getOptimizePluginOptions.ts
|
|
8
8
|
/**
|