@intlayer/babel 8.12.1 → 8.12.3
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.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +14 -9
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-minify.cjs +83 -0
- package/dist/cjs/babel-plugin-intlayer-minify.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +1 -0
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-purge.cjs +403 -0
- package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +293 -33
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
- package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
- package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
- package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
- package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/constants.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/detectPackageName.cjs +1 -0
- package/dist/cjs/extractContent/utils/detectPackageName.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs +1 -0
- package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/generateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getComponentName.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getExistingIntlayerInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getOrGenerateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs +1 -0
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
- package/dist/cjs/extractScriptBlocks.cjs +1 -0
- package/dist/cjs/extractScriptBlocks.cjs.map +1 -1
- package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
- package/dist/cjs/getOptimizePluginOptions.cjs +1 -0
- package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
- package/dist/cjs/getPurgePluginOptions.cjs +108 -0
- package/dist/cjs/getPurgePluginOptions.cjs.map +1 -0
- package/dist/cjs/index.cjs +12 -1
- package/dist/cjs/transformers.cjs +22 -7
- package/dist/cjs/transformers.cjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +14 -9
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-minify.mjs +82 -0
- package/dist/esm/babel-plugin-intlayer-minify.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-purge.mjs +400 -0
- package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +293 -34
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
- package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
- package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
- package/dist/esm/extractContent/extractContent.mjs.map +1 -1
- package/dist/esm/extractContent/processTsxFile.mjs.map +1 -1
- package/dist/esm/extractContent/utils/constants.mjs.map +1 -1
- package/dist/esm/extractContent/utils/detectPackageName.mjs.map +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs.map +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/generateKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getComponentName.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getExistingIntlayerInfo.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getOrGenerateKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/shouldExtract.mjs.map +1 -1
- package/dist/esm/extractScriptBlocks.mjs.map +1 -1
- package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
- package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
- package/dist/esm/getPurgePluginOptions.mjs +106 -0
- package/dist/esm/getPurgePluginOptions.mjs.map +1 -0
- package/dist/esm/index.mjs +7 -4
- package/dist/esm/transformers.mjs +20 -8
- package/dist/esm/transformers.mjs.map +1 -1
- package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-minify.d.ts +84 -0
- package/dist/types/babel-plugin-intlayer-minify.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-purge.d.ts +127 -0
- package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +72 -4
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
- package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
- package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
- package/dist/types/extractContent/extractContent.d.ts.map +1 -1
- package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
- package/dist/types/extractContent/utils/detectPackageName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/generateKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getComponentName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/shouldExtract.d.ts.map +1 -1
- package/dist/types/extractScriptBlocks.d.ts.map +1 -1
- package/dist/types/getExtractPluginOptions.d.ts.map +1 -1
- package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
- package/dist/types/getPurgePluginOptions.d.ts +83 -0
- package/dist/types/getPurgePluginOptions.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -3
- package/dist/types/transformers.d.ts +17 -5
- package/dist/types/transformers.d.ts.map +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-field-rename.mjs","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\n return quotient === 0 && ALPHABET[remainder]\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 * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\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.nodeType === 'translation' &&\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);\n }\n\n // Enumeration node: user-defined keys live inside enumeration.\n if (\n record.nodeType === 'enumeration' &&\n record.enumeration &&\n typeof record.enumeration === 'object' &&\n !Array.isArray(record.enumeration)\n ) {\n const values = Object.values(\n record.enumeration as Record<string, unknown>\n );\n if (values.length > 0) {\n return buildNestedRenameMapFromContent(values[0]);\n }\n }\n\n // All other intlayer nodes (pluralization, conditions, etc.) resolve to a\n // single value at runtime – return an empty map so they are treated\n // as leaves unless they contain nested user records.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\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 *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\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 // eslint-disable-next-line no-constant-condition\n while (true) {\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\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\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 computed 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 * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key the function recursively walks references to the\n * newly-bound local variable, calling both `walkRenameChain` (for subsequent\n * member-access chains like `validationErrors.invalidUrl`) and itself (for\n * further levels of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n for (const property of (parentNode.id as BabelTypes.ObjectPattern)\n .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 = renameMap.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 }\n property.key = babelTypes.identifier(renameEntry.shortName);\n\n // Recursively walk references to the local variable bound by this key.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarName = (property.value as BabelTypes.Identifier).name;\n const localVarBinding = refPath.scope.getBinding(localVarName);\n if (localVarBinding) {\n for (const nestedRefPath of localVarBinding.referencePaths) {\n walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);\n walkObjectDestructuring(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n }\n }\n }\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 * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\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 firstArgument.quasis[0]\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 and secondary destructurings\n // 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 walkObjectDestructuring(\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 // Direct access: result.fieldA or const { fieldA } = result\n walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n walkObjectDestructuring(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n walkRenameChain(babelTypes, callPath, fieldRenameMap);\n walkObjectDestructuring(\n babelTypes,\n callPath,\n fieldRenameMap\n );\n }\n }\n }\n }\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-field-rename.mjs","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\n return quotient === 0 && ALPHABET[remainder]\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 * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\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.nodeType === 'translation' &&\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);\n }\n\n // Enumeration node: user-defined keys live inside enumeration.\n if (\n record.nodeType === 'enumeration' &&\n record.enumeration &&\n typeof record.enumeration === 'object' &&\n !Array.isArray(record.enumeration)\n ) {\n const values = Object.values(\n record.enumeration as Record<string, unknown>\n );\n if (values.length > 0) {\n return buildNestedRenameMapFromContent(values[0]);\n }\n }\n\n // All other intlayer nodes (pluralization, conditions, etc.) resolve to a\n // single value at runtime – return an empty map so they are treated\n // as leaves unless they contain nested user records.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\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 *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): {\n finalPath: NodePath<BabelTypes.Node>;\n finalRenameMap: NestedRenameMap;\n} => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\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\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\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 computed 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 return { finalPath: refPath, finalRenameMap: renameMap };\n};\n\n/**\n * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key the function recursively walks references to the\n * newly-bound local variable, calling both `walkRenameChain` (for subsequent\n * member-access chains like `validationErrors.invalidUrl`) and itself (for\n * further levels of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n for (const property of (parentNode.id as BabelTypes.ObjectPattern)\n .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 = renameMap.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 }\n property.key = babelTypes.identifier(renameEntry.shortName);\n\n // Recursively walk references to the local variable bound by this key.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarName = (property.value as BabelTypes.Identifier).name;\n const localVarBinding = refPath.scope.getBinding(localVarName);\n if (localVarBinding) {\n for (const nestedRefPath of localVarBinding.referencePaths) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n }\n }\n }\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 * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\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 firstArgument.quasis[0]\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 and secondary destructurings\n // 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 const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n refPath,\n renameEntry.children\n );\n walkObjectDestructuring(\n babelTypes,\n finalPath,\n finalRenameMap\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 const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n callExpressionPath,\n fieldRenameMap\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\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 // Direct access: result.fieldA or const { fieldA } = result\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n walkObjectDestructuring(\n babelTypes,\n finalPath,\n finalRenameMap\n );\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n const {\n finalPath: signalFinalPath,\n finalRenameMap: signalFinalRenameMap,\n } = walkRenameChain(babelTypes, callPath, fieldRenameMap);\n walkObjectDestructuring(\n babelTypes,\n signalFinalPath,\n signalFinalRenameMap\n );\n }\n }\n }\n }\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { makeFieldRenameBabelPlugin } from "./babel-plugin-intlayer-field-rename.mjs";
|
|
2
|
+
import { INTLAYER_USAGE_REGEX } from "./transformers.mjs";
|
|
3
|
+
import { getSharedPruneContext } from "./babel-plugin-intlayer-purge.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/babel-plugin-intlayer-minify.ts
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin that rewrites component source files to use the short
|
|
8
|
+
* alphabetic aliases assigned to dictionary content fields during the
|
|
9
|
+
* minification pass (e.g. `content.title` → `content.a`).
|
|
10
|
+
*
|
|
11
|
+
* This plugin relies on the field-rename map built by
|
|
12
|
+
* {@link intlayerPurgeBabelPlugin} during its `pre()` hook. Both plugins
|
|
13
|
+
* share the same module-level {@link PruneContext} via
|
|
14
|
+
* {@link getSharedPruneContext}, so **`intlayerPurgeBabelPlugin` must be
|
|
15
|
+
* listed before this plugin** in `babel.config.js` so that its `pre()` runs
|
|
16
|
+
* first and the shared context is populated before this plugin's
|
|
17
|
+
* `Program.exit` visitor fires.
|
|
18
|
+
*
|
|
19
|
+
* All option values must be pre-resolved via {@link getMinifyPluginOptions}.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```js
|
|
23
|
+
* // babel.config.js
|
|
24
|
+
* const {
|
|
25
|
+
* intlayerPurgeBabelPlugin,
|
|
26
|
+
* intlayerMinifyBabelPlugin,
|
|
27
|
+
* intlayerOptimizeBabelPlugin,
|
|
28
|
+
* getPurgePluginOptions,
|
|
29
|
+
* getMinifyPluginOptions,
|
|
30
|
+
* getOptimizePluginOptions,
|
|
31
|
+
* } = require("@intlayer/babel");
|
|
32
|
+
*
|
|
33
|
+
* module.exports = {
|
|
34
|
+
* presets: ["next/babel"],
|
|
35
|
+
* plugins: [
|
|
36
|
+
* [intlayerPurgeBabelPlugin, getPurgePluginOptions()],
|
|
37
|
+
* [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],
|
|
38
|
+
* [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],
|
|
39
|
+
* ],
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* - This plugin is a no-op when `minify` is `false`, `optimize` is `false`,
|
|
45
|
+
* or `editorEnabled` is `true`.
|
|
46
|
+
* - Source-code renames must run **before** {@link intlayerOptimizeBabelPlugin}
|
|
47
|
+
* because the optimize pass replaces `useIntlayer` calls with
|
|
48
|
+
* `useDictionary`, erasing the dictionary-key information needed to look up
|
|
49
|
+
* the rename map.
|
|
50
|
+
*/
|
|
51
|
+
const intlayerMinifyBabelPlugin = (babel) => {
|
|
52
|
+
/**
|
|
53
|
+
* The field-rename `Program.exit` handler extracted from
|
|
54
|
+
* {@link makeFieldRenameBabelPlugin}. Resolved once per plugin-instance
|
|
55
|
+
* (i.e. once per babel.config.js registration, not once per file).
|
|
56
|
+
*/
|
|
57
|
+
let fieldRenameExitVisitor = null;
|
|
58
|
+
/** The `baseDir` for which the visitor was last resolved. */
|
|
59
|
+
let resolvedBaseDir = null;
|
|
60
|
+
return {
|
|
61
|
+
name: "intlayer-minify",
|
|
62
|
+
pre() {
|
|
63
|
+
const { baseDir, minify, optimize, editorEnabled } = this.opts;
|
|
64
|
+
if (!minify || optimize === false || editorEnabled) return;
|
|
65
|
+
if (resolvedBaseDir === baseDir && fieldRenameExitVisitor !== null) return;
|
|
66
|
+
const pruneContext = getSharedPruneContext(baseDir);
|
|
67
|
+
if (!pruneContext || pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;
|
|
68
|
+
resolvedBaseDir = baseDir;
|
|
69
|
+
const programVisitor = makeFieldRenameBabelPlugin(pruneContext)(babel).visitor.Program;
|
|
70
|
+
if (programVisitor && typeof programVisitor === "object" && "exit" in programVisitor && typeof programVisitor.exit === "function") fieldRenameExitVisitor = programVisitor.exit;
|
|
71
|
+
},
|
|
72
|
+
visitor: { Program: { exit(programPath, state) {
|
|
73
|
+
if (!fieldRenameExitVisitor) return;
|
|
74
|
+
if (!INTLAYER_USAGE_REGEX.test(state.file.code)) return;
|
|
75
|
+
fieldRenameExitVisitor(programPath);
|
|
76
|
+
} } }
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { intlayerMinifyBabelPlugin };
|
|
82
|
+
//# sourceMappingURL=babel-plugin-intlayer-minify.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-minify.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-minify.ts"],"sourcesContent":["import type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport { getSharedPruneContext } from './babel-plugin-intlayer-purge';\nimport type { PruneContext } from './babel-plugin-intlayer-usage-analyzer';\nimport { INTLAYER_USAGE_REGEX } from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerMinifyBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getMinifyPluginOptions}) so the plugin does not read the intlayer\n * configuration file on every file transform.\n */\nexport type MinifyPluginOptions = {\n /**\n * Absolute path to the project root. Used to look up the shared\n * {@link PruneContext} built by {@link intlayerPurgeBabelPlugin}.\n */\n baseDir: string;\n\n /**\n * When `true`, rewrite source-file property accesses to use the short\n * alphabetic aliases assigned during the minification pass\n * (e.g. `content.title` → `content.a`). Mirrors `build.minify`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. When explicitly `false`, the plugin is a\n * no-op. Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin is a no-op. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that rewrites component source files to use the short\n * alphabetic aliases assigned to dictionary content fields during the\n * minification pass (e.g. `content.title` → `content.a`).\n *\n * This plugin relies on the field-rename map built by\n * {@link intlayerPurgeBabelPlugin} during its `pre()` hook. Both plugins\n * share the same module-level {@link PruneContext} via\n * {@link getSharedPruneContext}, so **`intlayerPurgeBabelPlugin` must be\n * listed before this plugin** in `babel.config.js` so that its `pre()` runs\n * first and the shared context is populated before this plugin's\n * `Program.exit` visitor fires.\n *\n * All option values must be pre-resolved via {@link getMinifyPluginOptions}.\n *\n * @example\n * ```js\n * // babel.config.js\n * const {\n * intlayerPurgeBabelPlugin,\n * intlayerMinifyBabelPlugin,\n * intlayerOptimizeBabelPlugin,\n * getPurgePluginOptions,\n * getMinifyPluginOptions,\n * getOptimizePluginOptions,\n * } = require(\"@intlayer/babel\");\n *\n * module.exports = {\n * presets: [\"next/babel\"],\n * plugins: [\n * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],\n * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],\n * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n * ],\n * };\n * ```\n *\n * @remarks\n * - This plugin is a no-op when `minify` is `false`, `optimize` is `false`,\n * or `editorEnabled` is `true`.\n * - Source-code renames must run **before** {@link intlayerOptimizeBabelPlugin}\n * because the optimize pass replaces `useIntlayer` calls with\n * `useDictionary`, erasing the dictionary-key information needed to look up\n * the rename map.\n */\nexport const intlayerMinifyBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj => {\n /**\n * The field-rename `Program.exit` handler extracted from\n * {@link makeFieldRenameBabelPlugin}. Resolved once per plugin-instance\n * (i.e. once per babel.config.js registration, not once per file).\n */\n let fieldRenameExitVisitor:\n | ((programPath: NodePath<BabelTypes.Program>) => void)\n | null = null;\n\n /** The `baseDir` for which the visitor was last resolved. */\n let resolvedBaseDir: string | null = null;\n\n return {\n name: 'intlayer-minify',\n\n pre(this: PluginPass & { opts: MinifyPluginOptions }) {\n const { baseDir, minify, optimize, editorEnabled } = this.opts;\n\n if (!minify || optimize === false || editorEnabled) return;\n\n // Re-resolve when the baseDir changes (edge case in monorepos where the\n // same process handles multiple workspaces with different configs).\n if (resolvedBaseDir === baseDir && fieldRenameExitVisitor !== null)\n return;\n\n const pruneContext: PruneContext | null = getSharedPruneContext(baseDir);\n if (\n !pruneContext ||\n pruneContext.dictionaryKeyToFieldRenameMap.size === 0\n )\n return;\n\n resolvedBaseDir = baseDir;\n\n // Instantiate makeFieldRenameBabelPlugin and extract its Program.exit\n // handler so we can invoke it from our own visitor.\n const fieldRenamePlugin = makeFieldRenameBabelPlugin(pruneContext)(babel);\n const programVisitor = fieldRenamePlugin.visitor.Program;\n\n if (\n programVisitor &&\n typeof programVisitor === 'object' &&\n 'exit' in programVisitor &&\n typeof (\n programVisitor as {\n exit: (path: NodePath<BabelTypes.Program>) => void;\n }\n ).exit === 'function'\n ) {\n fieldRenameExitVisitor = (\n programVisitor as {\n exit: (path: NodePath<BabelTypes.Program>) => void;\n }\n ).exit;\n }\n },\n\n visitor: {\n Program: {\n exit(\n programPath: NodePath<BabelTypes.Program>,\n state: PluginPass\n ): void {\n if (!fieldRenameExitVisitor) return;\n if (!INTLAYER_USAGE_REGEX.test(state.file.code)) return;\n\n fieldRenameExitVisitor(programPath);\n },\n },\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,MAAa,6BAA6B,UAEzB;;;;;;CAMf,IAAI,yBAEO;;CAGX,IAAI,kBAAiC;AAErC,QAAO;EACL,MAAM;EAEN,MAAsD;GACpD,MAAM,EAAE,SAAS,QAAQ,UAAU,kBAAkB,KAAK;AAE1D,OAAI,CAAC,UAAU,aAAa,SAAS,cAAe;AAIpD,OAAI,oBAAoB,WAAW,2BAA2B,KAC5D;GAEF,MAAM,eAAoC,sBAAsB,QAAQ;AACxE,OACE,CAAC,gBACD,aAAa,8BAA8B,SAAS,EAEpD;AAEF,qBAAkB;GAKlB,MAAM,iBADoB,2BAA2B,aAAa,CAAC,MAC3B,CAAC,QAAQ;AAEjD,OACE,kBACA,OAAO,mBAAmB,YAC1B,UAAU,kBACV,OACE,eAGA,SAAS,WAEX,0BACE,eAGA;;EAIN,SAAS,EACP,SAAS,EACP,KACE,aACA,OACM;AACN,OAAI,CAAC,uBAAwB;AAC7B,OAAI,CAAC,qBAAqB,KAAK,MAAM,KAAK,KAAK,CAAE;AAEjD,0BAAuB,YAAY;KAEtC,EACF;EACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-optimize.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"sourcesContent":["import { dirname, join, relative } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { getPathHash } from '@intlayer/chokidar/utils';\nimport { normalizePath } from '@intlayer/config/utils';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'svelte-intlayer',\n 'vue-intlayer',\n 'angular-intlayer',\n 'preact-intlayer',\n 'solid-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n];\n\nconst CALLER_LIST = ['useIntlayer', 'getIntlayer'] as const;\n\n/**\n * Packages that support dynamic import\n */\nconst PACKAGE_LIST_DYNAMIC = [\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'preact-intlayer',\n 'vue-intlayer',\n 'solid-intlayer',\n 'svelte-intlayer',\n 'angular-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n] as const;\n\nconst STATIC_IMPORT_FUNCTION = {\n getIntlayer: 'getDictionary',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\n/**\n * Options for the optimization Babel plugin\n */\nexport type OptimizePluginOptions = {\n /**\n * If false, the plugin will not apply any transformation.\n */\n optimize?: boolean;\n /**\n * The path to the dictionaries directory.\n */\n dictionariesDir: string;\n /**\n * The path to the dictionaries entry file.\n */\n dictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries entry file.\n */\n unmergedDictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries directory.\n */\n unmergedDictionariesDir: string;\n /**\n * The path to the dictionaries directory.\n */\n dynamicDictionariesDir: string;\n /**\n * The path to the dynamic dictionaries entry file.\n */\n dynamicDictionariesEntryPath: string;\n /**\n * The path to the fetch dictionaries directory.\n */\n fetchDictionariesDir: string;\n /**\n * The path to the fetch dictionaries entry file.\n */\n fetchDictionariesEntryPath: string;\n /**\n * If true, the plugin will replace the dictionary entry file with `export default {}`.\n */\n replaceDictionaryEntry: boolean;\n /**\n * If true, the plugin will activate the dynamic import of the dictionaries. It will rely on Suspense to load the dictionaries.\n */\n importMode: 'static' | 'dynamic' | 'fetch' | undefined;\n /**\n * Map of dictionary keys to their specific import mode.\n */\n dictionaryModeMap?: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n /**\n * Files list to traverse.\n */\n filesList: string[];\n};\n\ntype State = PluginPass & {\n opts: OptimizePluginOptions;\n /** map key → generated ident (per-file) for static imports */\n _newStaticImports?: Map<string, BabelTypes.Identifier>;\n /** map key → generated ident (per-file) for dynamic imports */\n _newDynamicImports?: Map<string, BabelTypes.Identifier>;\n /** whether the current file imported *any* intlayer package */\n _hasValidImport?: boolean;\n /** map from local identifier name to the imported intlayer func name ('useIntlayer' | 'getIntlayer') */\n _callerMap?: Map<string, (typeof CALLER_LIST)[number]>;\n /** whether the current file *is* the dictionaries entry file */\n _isDictEntry?: boolean;\n /** whether dynamic helpers are active for this file */\n _useDynamicHelpers?: boolean;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n};\n\n/**\n * Replicates the xxHash64 → Base-62 algorithm used by the SWC version\n * and prefixes an underscore so the generated identifiers never collide\n * with user-defined ones.\n */\nconst makeIdent = (\n key: string,\n t: typeof BabelTypes\n): BabelTypes.Identifier => {\n const hash = getPathHash(key);\n return t.identifier(`_${hash}`);\n};\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch'\n): string => {\n let relativePath = join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n relativePath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n relativePath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n let rel = relative(dirname(fromFile), relativePath);\n\n // Fix windows path\n rel = normalizePath(rel);\n\n // Fix relative path\n if (!rel.startsWith('./') && !rel.startsWith('../')) {\n rel = `./${rel}`;\n }\n\n return rel;\n};\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()` and `getIntlayer()` from various Intlayer\n * packages into optimized dictionary access patterns, automatically importing the required\n * dictionary files based on the configured import mode.\n *\n * ## Supported Input Patterns\n *\n * The plugin recognizes these function calls:\n *\n * ```ts\n * // useIntlayer\n * import { useIntlayer } from 'react-intlayer';\n * import { useIntlayer } from 'next-intlayer';\n *\n * // getIntlayer\n * import { getIntlayer } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * ```\n *\n * ## Transformation Modes\n *\n * ### Static Mode (default: `importMode = \"static\"`)\n *\n * Imports JSON dictionaries directly and replaces function calls with dictionary access:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import { useDictionary as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash);\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Dynamic Mode (`importMode = \"dynamic\"`)\n *\n * Uses dynamic dictionary loading with Suspense support:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Live Mode (`importMode = \"live\"`)\n *\n * Uses live-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"live\" value:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_fetch, \"app\");\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * > If `dictionaryModeMap` does not include the key with \"live\" value, the plugin will fallback to the dynamic impor\n *\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n */\nexport const intlayerOptimizeBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-transform',\n\n pre() {\n this._newStaticImports = new Map();\n this._newDynamicImports = new Map();\n this._callerMap = new Map();\n this._isIncluded = true;\n this._hasValidImport = false;\n this._isDictEntry = false;\n this._useDynamicHelpers = false;\n\n // If optimize is false, skip processing entirely\n if (this.opts.optimize === false) {\n this._isIncluded = false;\n return;\n }\n\n // If filesList is provided, check if current file is included\n const filename = this.file.opts.filename;\n if (this.opts.filesList && filename) {\n const isIncluded = this.opts.filesList.includes(filename);\n\n if (!isIncluded) {\n // Force _isIncluded to false to skip processing\n this._isIncluded = false;\n return;\n }\n }\n },\n\n visitor: {\n /* If this file *is* the dictionaries entry, short-circuit: export {} */\n Program: {\n enter(programPath, state) {\n // Safe access to filename\n const filename = state.file.opts.filename;\n\n // Check if this is the correct file to transform\n\n if (\n state.opts.replaceDictionaryEntry &&\n filename === state.opts.dictionariesEntryPath\n ) {\n state._isDictEntry = true;\n\n // Traverse the program to surgically remove/edit specific parts\n programPath.traverse({\n // Remove all import statements (cleaning up 'sssss.json')\n ImportDeclaration(path) {\n path.remove();\n },\n\n // Find the variable definition and empty the object\n VariableDeclarator(path) {\n // We look for: const x = { ... }\n\n if (t.isObjectExpression(path.node.init)) {\n // Set the object properties to an empty array: {}\n path.node.init.properties = [];\n }\n },\n });\n\n // (Optional) Stop other plugins from processing this file further if needed\n // programPath.stop();\n }\n },\n\n /**\n * After full traversal, process imports and call expressions, then inject the JSON dictionary imports.\n *\n * We do the transformation in Program.exit (via a manual traverse) rather than using\n * top-level ImportDeclaration/CallExpression visitors. This ensures that if another plugin\n * (like babel-plugin-intlayer-extract) adds new useIntlayer calls in its Program.exit,\n * we will see and transform them here because our Program.exit runs after theirs.\n */\n exit(programPath, state) {\n if (state._isDictEntry) return; // nothing else to do – already replaced\n\n if (!state._isIncluded) return; // early-out if file is not included\n\n // Manual traversal to process imports and call expressions\n // This runs AFTER all other plugins' visitors have completed\n\n // Pre-pass to determine if we should use dynamic helpers\n let fileHasDynamicCall = false;\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (originalImportedName !== 'useIntlayer') return;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n fileHasDynamicCall = true;\n }\n },\n });\n\n programPath.traverse({\n /* Inspect every intlayer import */\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n // Mark that we do import from an intlayer package in this file\n state._hasValidImport = true;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (CALLER_LIST.includes(importedName as any)) {\n state._callerMap?.set(\n spec.local.name,\n importedName as (typeof CALLER_LIST)[number]\n );\n }\n\n const importMode = state.opts.importMode;\n // Determine whether this import should use the dynamic helpers.\n const shouldUseDynamicHelpers =\n (importMode === 'dynamic' ||\n importMode === 'fetch' ||\n fileHasDynamicCall) &&\n PACKAGE_LIST_DYNAMIC.includes(src as any);\n\n // Remember for later (CallExpression) whether we are using the dynamic helpers\n\n if (shouldUseDynamicHelpers) {\n state._useDynamicHelpers = true;\n }\n\n let helperMap: Record<string, string>;\n\n if (shouldUseDynamicHelpers) {\n // Use dynamic helpers for useIntlayer when dynamic mode is enabled\n helperMap = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n } as Record<string, string>;\n } else {\n // Use static helpers by default\n helperMap = STATIC_IMPORT_FUNCTION as Record<string, string>;\n }\n\n const newIdentifier = helperMap[importedName];\n\n // Only rewrite when we actually have a mapping for the imported\n // specifier (ignore unrelated named imports).\n\n if (newIdentifier) {\n // Keep the local alias intact (so calls remain `useIntlayer` /\n // `getIntlayer`), but rewrite the imported identifier so it\n // points to our helper implementation.\n spec.imported = t.identifier(newIdentifier);\n }\n }\n },\n\n /* Replace calls: useIntlayer(\"foo\") → useDictionary(_hash) or useDictionaryDynamic(_hash, \"foo\") */\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (!originalImportedName) return;\n\n // Ensure we ultimately emit helper imports for files that *invoke*\n // the hooks, even if they didn't import them directly (edge cases with\n // re-exports).\n state._hasValidImport = true;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const useDynamicHelpers = Boolean(state._useDynamicHelpers);\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'\n let perCallMode: 'static' | 'dynamic' | 'fetch' = 'static';\n\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (isUseIntlayer && useDynamicHelpers) {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic') {\n perCallMode = 'dynamic';\n } else if (importMode === 'fetch') {\n perCallMode = 'fetch';\n }\n } else if (isUseIntlayer && !useDynamicHelpers) {\n // If dynamic helpers are NOT active (global mode is static),\n // we STILL might want to force dynamic/live for this specific call\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n perCallMode = dictionaryOverrideMode;\n }\n }\n\n let ident: BabelTypes.Identifier;\n\n if (perCallMode === 'fetch') {\n // Use fetch dictionaries entry (live mode for selected keys)\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_fetch`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Helper: first argument is the dictionary entry, second is the key\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else if (perCallMode === 'dynamic') {\n // Use dynamic dictionaries entry\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n // Create a unique identifier for dynamic imports by appending a suffix\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_dyn`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Dynamic helper: first argument is the dictionary, second is the key.\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else {\n // Use static imports for getIntlayer or useIntlayer when not using dynamic helpers\n let staticIdent = state._newStaticImports?.get(key);\n\n if (!staticIdent) {\n staticIdent = makeIdent(key, t);\n state._newStaticImports?.set(key, staticIdent);\n }\n ident = staticIdent;\n\n // Static helper (useDictionary / getDictionary): replace key with ident.\n // After the splice above the key is always at index 0.\n path.node.arguments[0] = t.identifier(ident.name);\n }\n },\n });\n\n // Early-out if we touched nothing\n\n if (!state._hasValidImport) return;\n\n const file = state.file.opts.filename!;\n const dictionariesDir = state.opts.dictionariesDir;\n const dynamicDictionariesDir = state.opts.dynamicDictionariesDir;\n const fetchDictionariesDir = state.opts.fetchDictionariesDir;\n const imports: BabelTypes.ImportDeclaration[] = [];\n\n // Generate static JSON imports (getIntlayer always uses JSON dictionaries)\n for (const [key, ident] of state._newStaticImports!) {\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static'\n );\n\n const importDeclarationNode = t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n );\n\n // Add 'type: json' attribute for JSON files\n importDeclarationNode.attributes = [\n t.importAttribute(t.identifier('type'), t.stringLiteral('json')),\n ];\n\n imports.push(importDeclarationNode);\n }\n\n // Generate dynamic/fetch imports (for useIntlayer when using dynamic/live helpers)\n for (const [key, ident] of state._newDynamicImports!) {\n const modeForThisIdent: 'dynamic' | 'fetch' = ident.name.endsWith(\n '_fetch'\n )\n ? 'fetch'\n : 'dynamic';\n\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n modeForThisIdent\n );\n imports.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n )\n );\n }\n\n if (!imports.length) return;\n\n /* Keep \"use client\" / \"use server\" directives at the very top. */\n const bodyPaths = programPath.get(\n 'body'\n ) as NodePath<BabelTypes.Statement>[];\n let insertPos = 0;\n for (const stmtPath of bodyPaths) {\n const stmt = stmtPath.node;\n\n if (\n t.isExpressionStatement(stmt) &&\n t.isStringLiteral(stmt.expression) &&\n !stmt.expression.value.startsWith('import') &&\n !stmt.expression.value.startsWith('require')\n ) {\n insertPos += 1;\n } else {\n break;\n }\n }\n\n programPath.node.body.splice(insertPos, 0, ...imports);\n },\n },\n },\n };\n};\n"],"mappings":";;;;;AAMA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,cAAc,CAAC,eAAe,aAAa;;;;AAKjD,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,yBAAyB;CAC7B,aAAa;CACb,aAAa;AACf;AAEA,MAAM,0BAA0B,EAC9B,aAAa,uBACf;;;;;;AAsFA,MAAM,aACJ,KACA,MAC0B;CAC1B,MAAM,OAAO,YAAY,GAAG;CAC5B,OAAO,EAAE,WAAW,IAAI,MAAM;AAChC;AAEA,MAAM,iBACJ,UACA,iBACA,wBACA,sBACA,KACA,eACW;CACX,IAAI,eAAe,KAAK,iBAAiB,GAAG,IAAI,MAAM;CAEtD,IAAI,eAAe,SACjB,eAAe,KAAK,sBAAsB,GAAG,IAAI,KAAK;CAGxD,IAAI,eAAe,WACjB,eAAe,KAAK,wBAAwB,GAAG,IAAI,KAAK;CAG1D,IAAI,MAAM,SAAS,QAAQ,QAAQ,GAAG,YAAY;CAGlD,MAAM,cAAc,GAAG;CAGvB,IAAI,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,IAAI,WAAW,KAAK,GAChD,MAAM,KAAK;CAGb,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,MAAa,+BAA+B,UAEpB;CACtB,MAAM,EAAE,OAAO,MAAM;CAErB,OAAO;EACL,MAAM;EAEN,MAAM;GACJ,KAAK,oCAAoB,IAAI,IAAI;GACjC,KAAK,qCAAqB,IAAI,IAAI;GAClC,KAAK,6BAAa,IAAI,IAAI;GAC1B,KAAK,cAAc;GACnB,KAAK,kBAAkB;GACvB,KAAK,eAAe;GACpB,KAAK,qBAAqB;GAG1B,IAAI,KAAK,KAAK,aAAa,OAAO;IAChC,KAAK,cAAc;IACnB;GACF;GAGA,MAAM,WAAW,KAAK,KAAK,KAAK;GAChC,IAAI,KAAK,KAAK,aAAa,UAGzB;QAAI,CAFe,KAAK,KAAK,UAAU,SAAS,QAElC,GAAG;KAEf,KAAK,cAAc;KACnB;IACF;;EAEJ;EAEA,SAAS,EAEP,SAAS;GACP,MAAM,aAAa,OAAO;IAExB,MAAM,WAAW,MAAM,KAAK,KAAK;IAIjC,IACE,MAAM,KAAK,0BACX,aAAa,MAAM,KAAK,uBACxB;KACA,MAAM,eAAe;KAGrB,YAAY,SAAS;MAEnB,kBAAkB,MAAM;OACtB,KAAK,OAAO;MACd;MAGA,mBAAmB,MAAM;OAGvB,IAAI,EAAE,mBAAmB,KAAK,KAAK,IAAI,GAErC,KAAK,KAAK,KAAK,aAAa,CAAC;MAEjC;KACF,CAAC;IAIH;GACF;;;;;;;;;GAUA,KAAK,aAAa,OAAO;IACvB,IAAI,MAAM,cAAc;IAExB,IAAI,CAAC,MAAM,aAAa;IAMxB,IAAI,qBAAqB;IACzB,YAAY,SAAS,EACnB,eAAe,MAAM;KACnB,MAAM,SAAS,KAAK,KAAK;KAEzB,IAAI,CAAC,EAAE,aAAa,MAAM,GAAG;KAG7B,IAD6B,MAAM,YAAY,IAAI,OAAO,IAAI,MACjC,eAAe;KAE5C,MAAM,MAAM,KAAK,KAAK,UAAU;KAChC,IAAI;KACJ,IAAI,OAAO,EAAE,gBAAgB,GAAG,GAC9B,MAAM,IAAI;UACL,IACL,OACA,EAAE,kBAAkB,GAAG,KACvB,IAAI,YAAY,WAAW,KAC3B,IAAI,OAAO,WAAW,GAEtB,MAAM,IAAI,OAAO,IAAI,MAAM,UAAU,IAAI,OAAO,IAAI,MAAM;KAE5D,IAAI,CAAC,KAAK;KACV,MAAM,yBACJ,MAAM,KAAK,oBAAoB;KAEjC,IACE,2BAA2B,aAC3B,2BAA2B,SAE3B,qBAAqB;IAEzB,EACF,CAAC;IAED,YAAY,SAAS;KAEnB,kBAAkB,MAAM;MACtB,MAAM,MAAM,KAAK,KAAK,OAAO;MAE7B,IAAI,CAAC,aAAa,SAAS,GAAG,GAAG;MAGjC,MAAM,kBAAkB;MAExB,KAAK,MAAM,QAAQ,KAAK,KAAK,YAAY;OACvC,IAAI,CAAC,EAAE,kBAAkB,IAAI,GAAG;OAEhC,MAAM,eAAe,EAAE,aAAa,KAAK,QAAQ,IAC7C,KAAK,SAAS,OACb,KAAK,SAAsC;OAEhD,IAAI,YAAY,SAAS,YAAmB,GAC1C,MAAM,YAAY,IAChB,KAAK,MAAM,MACX,YACF;OAGF,MAAM,aAAa,MAAM,KAAK;OAE9B,MAAM,2BACH,eAAe,aACd,eAAe,WACf,uBACF,qBAAqB,SAAS,GAAU;OAI1C,IAAI,yBACF,MAAM,qBAAqB;OAG7B,IAAI;OAEJ,IAAI,yBAEF,YAAY;QACV,GAAG;QACH,GAAG;OACL;YAGA,YAAY;OAGd,MAAM,gBAAgB,UAAU;OAKhC,IAAI,eAIF,KAAK,WAAW,EAAE,WAAW,aAAa;MAE9C;KACF;KAGA,eAAe,MAAM;MACnB,MAAM,SAAS,KAAK,KAAK;MAEzB,IAAI,CAAC,EAAE,aAAa,MAAM,GAAG;MAE7B,MAAM,uBAAuB,MAAM,YAAY,IAAI,OAAO,IAAI;MAC9D,IAAI,CAAC,sBAAsB;MAK3B,MAAM,kBAAkB;MAExB,MAAM,MAAM,KAAK,KAAK,UAAU;MAChC,IAAI;MACJ,IAAI,OAAO,EAAE,gBAAgB,GAAG,GAC9B,MAAM,IAAI;WACL,IACL,OACA,EAAE,kBAAkB,GAAG,KACvB,IAAI,YAAY,WAAW,KAC3B,IAAI,OAAO,WAAW,GAEtB,MAAM,IAAI,OAAO,IAAI,MAAM,UAAU,IAAI,OAAO,IAAI,MAAM;MAE5D,IAAI,CAAC,KAAK;MAEV,MAAM,aAAa,MAAM,KAAK;MAC9B,MAAM,gBAAgB,yBAAyB;MAC/C,MAAM,oBAAoB,QAAQ,MAAM,kBAAkB;MAG1D,IAAI,cAA8C;MAElD,MAAM,yBACJ,MAAM,KAAK,oBAAoB;MAEjC,IAAI,iBAAiB,mBACnB;WAAI,wBACF,cAAc;YACT,IAAI,eAAe,WACxB,cAAc;YACT,IAAI,eAAe,SACxB,cAAc;MAChB,OACK,IAAI,iBAAiB,CAAC,mBAI3B;WACE,2BAA2B,aAC3B,2BAA2B,SAE3B,cAAc;MAChB;MAGF,IAAI;MAEJ,IAAI,gBAAgB,SAAS;OAE3B,IAAI,eAAe,MAAM,oBAAoB,IAAI,GAAG;OAEpD,IAAI,CAAC,cAAc;QACjB,MAAM,OAAO,YAAY,GAAG;QAC5B,eAAe,EAAE,WAAW,IAAI,KAAK,OAAO;QAC5C,MAAM,oBAAoB,IAAI,KAAK,YAAY;OACjD;OACA,QAAQ;OAGR,KAAK,KAAK,YAAY,CACpB,EAAE,WAAW,MAAM,IAAI,GACvB,GAAG,KAAK,KAAK,SACf;MACF,OAAO,IAAI,gBAAgB,WAAW;OAEpC,IAAI,eAAe,MAAM,oBAAoB,IAAI,GAAG;OAEpD,IAAI,CAAC,cAAc;QAEjB,MAAM,OAAO,YAAY,GAAG;QAC5B,eAAe,EAAE,WAAW,IAAI,KAAK,KAAK;QAC1C,MAAM,oBAAoB,IAAI,KAAK,YAAY;OACjD;OACA,QAAQ;OAGR,KAAK,KAAK,YAAY,CACpB,EAAE,WAAW,MAAM,IAAI,GACvB,GAAG,KAAK,KAAK,SACf;MACF,OAAO;OAEL,IAAI,cAAc,MAAM,mBAAmB,IAAI,GAAG;OAElD,IAAI,CAAC,aAAa;QAChB,cAAc,UAAU,KAAK,CAAC;QAC9B,MAAM,mBAAmB,IAAI,KAAK,WAAW;OAC/C;OACA,QAAQ;OAIR,KAAK,KAAK,UAAU,KAAK,EAAE,WAAW,MAAM,IAAI;MAClD;KACF;IACF,CAAC;IAID,IAAI,CAAC,MAAM,iBAAiB;IAE5B,MAAM,OAAO,MAAM,KAAK,KAAK;IAC7B,MAAM,kBAAkB,MAAM,KAAK;IACnC,MAAM,yBAAyB,MAAM,KAAK;IAC1C,MAAM,uBAAuB,MAAM,KAAK;IACxC,MAAM,UAA0C,CAAC;IAGjD,KAAK,MAAM,CAAC,KAAK,UAAU,MAAM,mBAAoB;KACnD,MAAM,MAAM,cACV,MACA,iBACA,wBACA,sBACA,KACA,QACF;KAEA,MAAM,wBAAwB,EAAE,kBAC9B,CAAC,EAAE,uBAAuB,EAAE,WAAW,MAAM,IAAI,CAAC,CAAC,GACnD,EAAE,cAAc,GAAG,CACrB;KAGA,sBAAsB,aAAa,CACjC,EAAE,gBAAgB,EAAE,WAAW,MAAM,GAAG,EAAE,cAAc,MAAM,CAAC,CACjE;KAEA,QAAQ,KAAK,qBAAqB;IACpC;IAGA,KAAK,MAAM,CAAC,KAAK,UAAU,MAAM,oBAAqB;KAOpD,MAAM,MAAM,cACV,MACA,iBACA,wBACA,sBACA,KAX4C,MAAM,KAAK,SACvD,QACF,IACI,UACA,SASJ;KACA,QAAQ,KACN,EAAE,kBACA,CAAC,EAAE,uBAAuB,EAAE,WAAW,MAAM,IAAI,CAAC,CAAC,GACnD,EAAE,cAAc,GAAG,CACrB,CACF;IACF;IAEA,IAAI,CAAC,QAAQ,QAAQ;IAGrB,MAAM,YAAY,YAAY,IAC5B,MACF;IACA,IAAI,YAAY;IAChB,KAAK,MAAM,YAAY,WAAW;KAChC,MAAM,OAAO,SAAS;KAEtB,IACE,EAAE,sBAAsB,IAAI,KAC5B,EAAE,gBAAgB,KAAK,UAAU,KACjC,CAAC,KAAK,WAAW,MAAM,WAAW,QAAQ,KAC1C,CAAC,KAAK,WAAW,MAAM,WAAW,SAAS,GAE3C,aAAa;UAEb;IAEJ;IAEA,YAAY,KAAK,KAAK,OAAO,WAAW,GAAG,GAAG,OAAO;GACvD;EACF,EACF;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-optimize.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"sourcesContent":["import { dirname, join, relative } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { getPathHash } from '@intlayer/chokidar/utils';\nimport { normalizePath } from '@intlayer/config/utils';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'svelte-intlayer',\n 'vue-intlayer',\n 'angular-intlayer',\n 'preact-intlayer',\n 'solid-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n];\n\nconst CALLER_LIST = ['useIntlayer', 'getIntlayer'] as const;\n\n/**\n * Packages that support dynamic import\n */\nconst PACKAGE_LIST_DYNAMIC = [\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'preact-intlayer',\n 'vue-intlayer',\n 'solid-intlayer',\n 'svelte-intlayer',\n 'angular-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n] as const;\n\nconst STATIC_IMPORT_FUNCTION = {\n getIntlayer: 'getDictionary',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\n/**\n * Options for the optimization Babel plugin\n */\nexport type OptimizePluginOptions = {\n /**\n * If false, the plugin will not apply any transformation.\n */\n optimize?: boolean;\n /**\n * The path to the dictionaries directory.\n */\n dictionariesDir: string;\n /**\n * The path to the dictionaries entry file.\n */\n dictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries entry file.\n */\n unmergedDictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries directory.\n */\n unmergedDictionariesDir: string;\n /**\n * The path to the dictionaries directory.\n */\n dynamicDictionariesDir: string;\n /**\n * The path to the dynamic dictionaries entry file.\n */\n dynamicDictionariesEntryPath: string;\n /**\n * The path to the fetch dictionaries directory.\n */\n fetchDictionariesDir: string;\n /**\n * The path to the fetch dictionaries entry file.\n */\n fetchDictionariesEntryPath: string;\n /**\n * If true, the plugin will replace the dictionary entry file with `export default {}`.\n */\n replaceDictionaryEntry: boolean;\n /**\n * If true, the plugin will activate the dynamic import of the dictionaries. It will rely on Suspense to load the dictionaries.\n */\n importMode: 'static' | 'dynamic' | 'fetch' | undefined;\n /**\n * Map of dictionary keys to their specific import mode.\n */\n dictionaryModeMap?: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n /**\n * Files list to traverse.\n */\n filesList: string[];\n};\n\ntype State = PluginPass & {\n opts: OptimizePluginOptions;\n /** map key → generated ident (per-file) for static imports */\n _newStaticImports?: Map<string, BabelTypes.Identifier>;\n /** map key → generated ident (per-file) for dynamic imports */\n _newDynamicImports?: Map<string, BabelTypes.Identifier>;\n /** whether the current file imported *any* intlayer package */\n _hasValidImport?: boolean;\n /** map from local identifier name to the imported intlayer func name ('useIntlayer' | 'getIntlayer') */\n _callerMap?: Map<string, (typeof CALLER_LIST)[number]>;\n /** whether the current file *is* the dictionaries entry file */\n _isDictEntry?: boolean;\n /** whether dynamic helpers are active for this file */\n _useDynamicHelpers?: boolean;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n};\n\n/**\n * Replicates the xxHash64 → Base-62 algorithm used by the SWC version\n * and prefixes an underscore so the generated identifiers never collide\n * with user-defined ones.\n */\nconst makeIdent = (\n key: string,\n t: typeof BabelTypes\n): BabelTypes.Identifier => {\n const hash = getPathHash(key);\n return t.identifier(`_${hash}`);\n};\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch'\n): string => {\n let relativePath = join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n relativePath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n relativePath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n let rel = relative(dirname(fromFile), relativePath);\n\n // Fix windows path\n rel = normalizePath(rel);\n\n // Fix relative path\n if (!rel.startsWith('./') && !rel.startsWith('../')) {\n rel = `./${rel}`;\n }\n\n return rel;\n};\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()` and `getIntlayer()` from various Intlayer\n * packages into optimized dictionary access patterns, automatically importing the required\n * dictionary files based on the configured import mode.\n *\n * ## Supported Input Patterns\n *\n * The plugin recognizes these function calls:\n *\n * ```ts\n * // useIntlayer\n * import { useIntlayer } from 'react-intlayer';\n * import { useIntlayer } from 'next-intlayer';\n *\n * // getIntlayer\n * import { getIntlayer } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * ```\n *\n * ## Transformation Modes\n *\n * ### Static Mode (default: `importMode = \"static\"`)\n *\n * Imports JSON dictionaries directly and replaces function calls with dictionary access:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import { useDictionary as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash);\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Dynamic Mode (`importMode = \"dynamic\"`)\n *\n * Uses dynamic dictionary loading with Suspense support:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Live Mode (`importMode = \"live\"`)\n *\n * Uses live-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"live\" value:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_fetch, \"app\");\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * > If `dictionaryModeMap` does not include the key with \"live\" value, the plugin will fallback to the dynamic impor\n *\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n */\nexport const intlayerOptimizeBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-transform',\n\n pre() {\n this._newStaticImports = new Map();\n this._newDynamicImports = new Map();\n this._callerMap = new Map();\n this._isIncluded = true;\n this._hasValidImport = false;\n this._isDictEntry = false;\n this._useDynamicHelpers = false;\n\n // If optimize is false, skip processing entirely\n if (this.opts.optimize === false) {\n this._isIncluded = false;\n return;\n }\n\n // If filesList is provided, check if current file is included\n const filename = this.file.opts.filename;\n if (this.opts.filesList && filename) {\n const isIncluded = this.opts.filesList.includes(filename);\n\n if (!isIncluded) {\n // Force _isIncluded to false to skip processing\n this._isIncluded = false;\n return;\n }\n }\n },\n\n visitor: {\n /* If this file *is* the dictionaries entry, short-circuit: export {} */\n Program: {\n enter(programPath, state) {\n // Safe access to filename\n const filename = state.file.opts.filename;\n\n // Check if this is the correct file to transform\n\n if (\n state.opts.replaceDictionaryEntry &&\n filename === state.opts.dictionariesEntryPath\n ) {\n state._isDictEntry = true;\n\n // Traverse the program to surgically remove/edit specific parts\n programPath.traverse({\n // Remove all import statements (cleaning up 'sssss.json')\n ImportDeclaration(path) {\n path.remove();\n },\n\n // Find the variable definition and empty the object\n VariableDeclarator(path) {\n // We look for: const x = { ... }\n\n if (t.isObjectExpression(path.node.init)) {\n // Set the object properties to an empty array: {}\n path.node.init.properties = [];\n }\n },\n });\n\n // (Optional) Stop other plugins from processing this file further if needed\n // programPath.stop();\n }\n },\n\n /**\n * After full traversal, process imports and call expressions, then inject the JSON dictionary imports.\n *\n * We do the transformation in Program.exit (via a manual traverse) rather than using\n * top-level ImportDeclaration/CallExpression visitors. This ensures that if another plugin\n * (like babel-plugin-intlayer-extract) adds new useIntlayer calls in its Program.exit,\n * we will see and transform them here because our Program.exit runs after theirs.\n */\n exit(programPath, state) {\n if (state._isDictEntry) return; // nothing else to do – already replaced\n\n if (!state._isIncluded) return; // early-out if file is not included\n\n // Manual traversal to process imports and call expressions\n // This runs AFTER all other plugins' visitors have completed\n\n // Pre-pass to determine if we should use dynamic helpers\n let fileHasDynamicCall = false;\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (originalImportedName !== 'useIntlayer') return;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n fileHasDynamicCall = true;\n }\n },\n });\n\n programPath.traverse({\n /* Inspect every intlayer import */\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n // Mark that we do import from an intlayer package in this file\n state._hasValidImport = true;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (CALLER_LIST.includes(importedName as any)) {\n state._callerMap?.set(\n spec.local.name,\n importedName as (typeof CALLER_LIST)[number]\n );\n }\n\n const importMode = state.opts.importMode;\n // Determine whether this import should use the dynamic helpers.\n const shouldUseDynamicHelpers =\n (importMode === 'dynamic' ||\n importMode === 'fetch' ||\n fileHasDynamicCall) &&\n PACKAGE_LIST_DYNAMIC.includes(src as any);\n\n // Remember for later (CallExpression) whether we are using the dynamic helpers\n\n if (shouldUseDynamicHelpers) {\n state._useDynamicHelpers = true;\n }\n\n let helperMap: Record<string, string>;\n\n if (shouldUseDynamicHelpers) {\n // Use dynamic helpers for useIntlayer when dynamic mode is enabled\n helperMap = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n } as Record<string, string>;\n } else {\n // Use static helpers by default\n helperMap = STATIC_IMPORT_FUNCTION as Record<string, string>;\n }\n\n const newIdentifier = helperMap[importedName];\n\n // Only rewrite when we actually have a mapping for the imported\n // specifier (ignore unrelated named imports).\n\n if (newIdentifier) {\n // Keep the local alias intact (so calls remain `useIntlayer` /\n // `getIntlayer`), but rewrite the imported identifier so it\n // points to our helper implementation.\n spec.imported = t.identifier(newIdentifier);\n }\n }\n },\n\n /* Replace calls: useIntlayer(\"foo\") → useDictionary(_hash) or useDictionaryDynamic(_hash, \"foo\") */\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (!originalImportedName) return;\n\n // Ensure we ultimately emit helper imports for files that *invoke*\n // the hooks, even if they didn't import them directly (edge cases with\n // re-exports).\n state._hasValidImport = true;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const useDynamicHelpers = Boolean(state._useDynamicHelpers);\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'\n let perCallMode: 'static' | 'dynamic' | 'fetch' = 'static';\n\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (isUseIntlayer && useDynamicHelpers) {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic') {\n perCallMode = 'dynamic';\n } else if (importMode === 'fetch') {\n perCallMode = 'fetch';\n }\n } else if (isUseIntlayer && !useDynamicHelpers) {\n // If dynamic helpers are NOT active (global mode is static),\n // we STILL might want to force dynamic/live for this specific call\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n perCallMode = dictionaryOverrideMode;\n }\n }\n\n let ident: BabelTypes.Identifier;\n\n if (perCallMode === 'fetch') {\n // Use fetch dictionaries entry (live mode for selected keys)\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_fetch`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Helper: first argument is the dictionary entry, second is the key\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else if (perCallMode === 'dynamic') {\n // Use dynamic dictionaries entry\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n // Create a unique identifier for dynamic imports by appending a suffix\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_dyn`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Dynamic helper: first argument is the dictionary, second is the key.\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else {\n // Use static imports for getIntlayer or useIntlayer when not using dynamic helpers\n let staticIdent = state._newStaticImports?.get(key);\n\n if (!staticIdent) {\n staticIdent = makeIdent(key, t);\n state._newStaticImports?.set(key, staticIdent);\n }\n ident = staticIdent;\n\n // Static helper (useDictionary / getDictionary): replace key with ident.\n // After the splice above the key is always at index 0.\n path.node.arguments[0] = t.identifier(ident.name);\n }\n },\n });\n\n // Early-out if we touched nothing\n\n if (!state._hasValidImport) return;\n\n const file = state.file.opts.filename!;\n const dictionariesDir = state.opts.dictionariesDir;\n const dynamicDictionariesDir = state.opts.dynamicDictionariesDir;\n const fetchDictionariesDir = state.opts.fetchDictionariesDir;\n const imports: BabelTypes.ImportDeclaration[] = [];\n\n // Generate static JSON imports (getIntlayer always uses JSON dictionaries)\n for (const [key, ident] of state._newStaticImports!) {\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static'\n );\n\n const importDeclarationNode = t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n );\n\n // Add 'type: json' attribute for JSON files\n importDeclarationNode.attributes = [\n t.importAttribute(t.identifier('type'), t.stringLiteral('json')),\n ];\n\n imports.push(importDeclarationNode);\n }\n\n // Generate dynamic/fetch imports (for useIntlayer when using dynamic/live helpers)\n for (const [key, ident] of state._newDynamicImports!) {\n const modeForThisIdent: 'dynamic' | 'fetch' = ident.name.endsWith(\n '_fetch'\n )\n ? 'fetch'\n : 'dynamic';\n\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n modeForThisIdent\n );\n imports.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n )\n );\n }\n\n if (!imports.length) return;\n\n /* Keep \"use client\" / \"use server\" directives at the very top. */\n const bodyPaths = programPath.get(\n 'body'\n ) as NodePath<BabelTypes.Statement>[];\n let insertPos = 0;\n for (const stmtPath of bodyPaths) {\n const stmt = stmtPath.node;\n\n if (\n t.isExpressionStatement(stmt) &&\n t.isStringLiteral(stmt.expression) &&\n !stmt.expression.value.startsWith('import') &&\n !stmt.expression.value.startsWith('require')\n ) {\n insertPos += 1;\n } else {\n break;\n }\n }\n\n programPath.node.body.splice(insertPos, 0, ...imports);\n },\n },\n },\n };\n};\n"],"mappings":"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"}
|