@intlayer/babel 9.4.4 → 9.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +0 -2
  2. package/dist/cjs/analyzeContentUsage.cjs +301 -0
  3. package/dist/cjs/analyzeContentUsage.cjs.map +1 -0
  4. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +5 -4
  5. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
  6. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +171 -282
  7. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  8. package/dist/cjs/babel-plugin-intlayer-purge.cjs +4 -3
  9. package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
  10. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +49 -699
  11. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
  12. package/dist/cjs/compat/namespaceResolution.cjs +124 -0
  13. package/dist/cjs/compat/namespaceResolution.cjs.map +1 -0
  14. package/dist/cjs/compat/optimizePass.cjs +144 -0
  15. package/dist/cjs/compat/optimizePass.cjs.map +1 -0
  16. package/dist/cjs/compat/usageAnalysis.cjs +195 -0
  17. package/dist/cjs/compat/usageAnalysis.cjs.map +1 -0
  18. package/dist/cjs/dictionaryImports.cjs +40 -0
  19. package/dist/cjs/dictionaryImports.cjs.map +1 -0
  20. package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs +1 -1
  21. package/dist/cjs/getExtractPluginOptions.cjs +1 -1
  22. package/dist/cjs/getOptimizePluginOptions.cjs +2 -6
  23. package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
  24. package/dist/cjs/getPurgePluginOptions.cjs +1 -1
  25. package/dist/cjs/index.cjs +9 -5
  26. package/dist/cjs/nativeCallers.cjs +41 -0
  27. package/dist/cjs/nativeCallers.cjs.map +1 -0
  28. package/dist/cjs/pruneContext.cjs +79 -0
  29. package/dist/cjs/pruneContext.cjs.map +1 -0
  30. package/dist/cjs/staticAstReaders.cjs +140 -0
  31. package/dist/cjs/staticAstReaders.cjs.map +1 -0
  32. package/dist/cjs/transformers.cjs +2 -1
  33. package/dist/cjs/transformers.cjs.map +1 -1
  34. package/dist/esm/analyzeContentUsage.mjs +300 -0
  35. package/dist/esm/analyzeContentUsage.mjs.map +1 -0
  36. package/dist/esm/babel-plugin-intlayer-field-rename.mjs +3 -2
  37. package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
  38. package/dist/esm/babel-plugin-intlayer-optimize.mjs +171 -282
  39. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  40. package/dist/esm/babel-plugin-intlayer-purge.mjs +3 -2
  41. package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -1
  42. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +43 -694
  43. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
  44. package/dist/esm/compat/namespaceResolution.mjs +120 -0
  45. package/dist/esm/compat/namespaceResolution.mjs.map +1 -0
  46. package/dist/esm/compat/optimizePass.mjs +143 -0
  47. package/dist/esm/compat/optimizePass.mjs.map +1 -0
  48. package/dist/esm/compat/usageAnalysis.mjs +193 -0
  49. package/dist/esm/compat/usageAnalysis.mjs.map +1 -0
  50. package/dist/esm/dictionaryImports.mjs +38 -0
  51. package/dist/esm/dictionaryImports.mjs.map +1 -0
  52. package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs +1 -1
  53. package/dist/esm/getExtractPluginOptions.mjs +1 -1
  54. package/dist/esm/getOptimizePluginOptions.mjs +2 -6
  55. package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
  56. package/dist/esm/getPurgePluginOptions.mjs +1 -1
  57. package/dist/esm/index.mjs +5 -2
  58. package/dist/esm/nativeCallers.mjs +38 -0
  59. package/dist/esm/nativeCallers.mjs.map +1 -0
  60. package/dist/esm/pruneContext.mjs +76 -0
  61. package/dist/esm/pruneContext.mjs.map +1 -0
  62. package/dist/esm/staticAstReaders.mjs +129 -0
  63. package/dist/esm/staticAstReaders.mjs.map +1 -0
  64. package/dist/esm/transformers.mjs +2 -1
  65. package/dist/esm/transformers.mjs.map +1 -1
  66. package/dist/types/analyzeContentUsage.d.ts +18 -0
  67. package/dist/types/analyzeContentUsage.d.ts.map +1 -0
  68. package/dist/types/babel-plugin-intlayer-field-rename.d.ts +2 -1
  69. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  70. package/dist/types/babel-plugin-intlayer-optimize.d.ts +9 -15
  71. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  72. package/dist/types/babel-plugin-intlayer-purge.d.ts +3 -2
  73. package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
  74. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +20 -187
  75. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
  76. package/dist/types/compat/namespaceResolution.d.ts +66 -0
  77. package/dist/types/compat/namespaceResolution.d.ts.map +1 -0
  78. package/dist/types/compat/optimizePass.d.ts +58 -0
  79. package/dist/types/compat/optimizePass.d.ts.map +1 -0
  80. package/dist/types/compat/usageAnalysis.d.ts +75 -0
  81. package/dist/types/compat/usageAnalysis.d.ts.map +1 -0
  82. package/dist/types/dictionaryImports.d.ts +46 -0
  83. package/dist/types/dictionaryImports.d.ts.map +1 -0
  84. package/dist/types/index.d.ts +5 -2
  85. package/dist/types/nativeCallers.d.ts +33 -0
  86. package/dist/types/nativeCallers.d.ts.map +1 -0
  87. package/dist/types/pruneContext.d.ts +152 -0
  88. package/dist/types/pruneContext.d.ts.map +1 -0
  89. package/dist/types/staticAstReaders.d.ts +84 -0
  90. package/dist/types/staticAstReaders.d.ts.map +1 -0
  91. package/dist/types/transformers.d.ts +2 -1
  92. package/dist/types/transformers.d.ts.map +1 -1
  93. package/package.json +10 -10
@@ -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, PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n type CallerDescriptor,\n getRewritableCallers,\n} from '@intlayer/config/callers';\nimport { normalizePath } from '@intlayer/config/utils';\nimport { getPathHash } from '@intlayer/engine/utils';\nimport { getNormalizedFilesListSet } from './normalizedFilesList';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n '@intlayer/core/interpreter',\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', 'getIntlayerAsync'] 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 // `getIntlayerAsync` always reads a per-locale chunk, in every import mode,\n // so its helper is the same on both sides of the static/dynamic split.\n getIntlayerAsync: 'getDictionaryAsync',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\n/** Import rename table applied when the helper plan for a package is `dynamic`. */\nconst DYNAMIC_HELPER_MAP: Record<string, string> = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n};\n\n/**\n * Packages whose SSR-static `useDictionary` lives in a `/server` subpath\n * because it differs from the root one. Solid's reserves one hydration\n * resource slot so hydration ids stay aligned with the client's\n * `useDictionaryDynamic`; for other frameworks the root `useDictionary` is\n * already the correct SSR-static implementation.\n */\nconst SSR_STATIC_IMPORT_SOURCE: Partial<Record<string, string>> = {\n 'solid-intlayer': 'solid-intlayer/server',\n};\n\ntype CallerName = (typeof CALLER_LIST)[number];\ntype ImportMode = 'static' | 'dynamic' | 'fetch';\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 * Keys of the dictionaries that reference other dictionaries through\n * `nest()`.\n *\n * For those, the injected static import points at the generated companion\n * module (`<dictionariesDir>/nested/<key>.mjs`) instead of the raw JSON. The\n * companion re-exports the dictionary with its nest targets attached, so\n * `getNesting` resolves them from that local reference rather than from the\n * global registry this plugin empties — and each target lands in the chunk of\n * the dictionary referencing it.\n *\n * Dynamic and fetch modes need nothing here: their generated loaders already\n * attach the same targets per locale.\n */\n nestingDictionaryKeys?: string[];\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 * Whether the current transform is for an SSR bundle.\n */\n isServer?: boolean;\n /**\n * Compat-adapter caller descriptors injected by the compat packages'\n * bundler plugins (e.g. `@intlayer/react-i18next/plugin`).\n *\n * Callers carrying `staticReplacement` / `dynamicReplacement` are rewritten\n * the same way as native `useIntlayer` calls: the namespace string argument\n * is replaced by a pre-imported dictionary object and the import specifier\n * is re-pointed to the dictionary-accepting variant, e.g.\n * `useTranslation('about')` → `useDictionary(_dictHash)`.\n *\n * No compat-specific name is hard-coded in this plugin — the whole rewrite\n * is driven by these descriptors.\n */\n compatCallers?: CallerDescriptor[];\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 /** map from local identifier name to the intlayer package it was imported from */\n _callerPackageMap?: Map<string, string>;\n /** map from local identifier name to the compat caller descriptor it was imported as */\n _compatCallerMap?: Map<string, CallerDescriptor>;\n /** whether the current file *is* the dictionaries entry file */\n _isDictEntry?: 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\n/**\n * Builds the module specifier pointing at `targetPath` from `fromFile`, with\n * forward slashes and an explicit `./` prefix so bundlers never treat it as a\n * bare package specifier.\n */\nconst toRelativeSpecifier = (fromFile: string, targetPath: string): string => {\n // Fix windows path\n const relativePath = normalizePath(relative(dirname(fromFile), targetPath));\n\n // Fix relative path\n if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {\n return `./${relativePath}`;\n }\n\n return relativePath;\n};\n\n/**\n * Subdirectory of the compiled dictionaries holding the companion modules that\n * re-export a dictionary with its `nest()` targets attached. Mirrors\n * `NESTED_DICTIONARIES_SUBDIR` in `@intlayer/engine`.\n */\nconst NESTED_DICTIONARIES_SUBDIR = 'nested';\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch',\n hasNestedDictionaries = false\n): string => {\n // Static mode is the only one needing the companion: the dynamic and fetch\n // loaders already attach the nest targets per locale.\n let dictionaryPath = hasNestedDictionaries\n ? join(dictionariesDir, NESTED_DICTIONARIES_SUBDIR, `${key}.mjs`)\n : join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n dictionaryPath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n dictionaryPath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n return toRelativeSpecifier(fromFile, dictionaryPath);\n};\n\nconst getKeyFromArgument = (\n arg: BabelTypes.Node | null | undefined,\n t: typeof BabelTypes\n): string | undefined => {\n if (arg && t.isStringLiteral(arg)) {\n return arg.value;\n }\n\n if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n return arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n\n return undefined;\n};\n\nconst isCallerName = (name: string): name is CallerName =>\n CALLER_LIST.includes(name as CallerName);\n\n/**\n * Splits a namespace string at the first `.` to separate the dictionary key\n * from an optional key prefix for nested namespaces, mirroring the SWC\n * plugin's `split_namespace`.\n *\n * `'about'` → `{ dictionaryKey: 'about', keyPrefix: '' }`\n * `'about.counter'` → `{ dictionaryKey: 'about', keyPrefix: 'counter' }`\n */\nconst splitNamespace = (\n namespace: string\n): { dictionaryKey: string; keyPrefix: string } => {\n const dotPosition = namespace.indexOf('.');\n if (dotPosition === -1) return { dictionaryKey: namespace, keyPrefix: '' };\n return {\n dictionaryKey: namespace.slice(0, dotPosition),\n keyPrefix: namespace.slice(dotPosition + 1),\n };\n};\n\n/**\n * Result of statically resolving the namespace of a compat caller call-site.\n */\ntype CompatNamespaceMatch = {\n /** The full namespace string, e.g. `'about.counter'`. */\n fullNamespace: string;\n /** Index of the positional namespace argument, when read from an argument. */\n argumentIndex?: number;\n /**\n * The options-object property holding the namespace, when read from an\n * option. The rewrite mutates it in place (key-prefix remainder) or removes\n * it from `optionsObject`.\n */\n optionProperty?: BabelTypes.ObjectProperty;\n /** The options object owning `optionProperty`. */\n optionsObject?: BabelTypes.ObjectExpression;\n};\n\n/**\n * Statically resolves the namespace of a compat caller call-site from its\n * descriptor's `namespaceSources` (argument / option / fixed — the\n * per-message-id `path-first-segment` form cannot bind one dictionary to the\n * call site and is skipped). Returns `undefined` when the namespace is absent\n * or dynamic, in which case the call is left untouched and resolves through\n * the runtime dictionary registry.\n */\nconst resolveCompatNamespaceMatch = (\n t: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n descriptor: CallerDescriptor\n): CompatNamespaceMatch | undefined => {\n for (const source of descriptor.namespaceSources) {\n if (source.from === 'fixed') {\n return { fullNamespace: source.value };\n }\n\n if (source.from === 'argument') {\n const namespace = getKeyFromArgument(callArguments[source.index], t);\n if (namespace !== undefined) {\n return { fullNamespace: namespace, argumentIndex: source.index };\n }\n continue;\n }\n\n if (source.from === 'option') {\n const optionsArgument = callArguments[source.argumentIndex];\n if (!optionsArgument || !t.isObjectExpression(optionsArgument)) continue;\n\n for (const property of optionsArgument.properties) {\n if (!t.isObjectProperty(property)) continue;\n const keyMatches =\n (t.isIdentifier(property.key) &&\n property.key.name === source.property) ||\n (t.isStringLiteral(property.key) &&\n property.key.value === source.property);\n if (!keyMatches) continue;\n\n const namespace = getKeyFromArgument(property.value, t);\n if (namespace !== undefined) {\n return {\n fullNamespace: namespace,\n optionProperty: property,\n optionsObject: optionsArgument,\n };\n }\n }\n }\n }\n\n return undefined;\n};\n\nconst isDynamicPackage = (\n packageName: string\n): packageName is (typeof PACKAGE_LIST_DYNAMIC)[number] =>\n PACKAGE_LIST_DYNAMIC.includes(\n packageName as (typeof PACKAGE_LIST_DYNAMIC)[number]\n );\n\n/**\n * Helper family every `useIntlayer`/`getIntlayer` call from one package import\n * resolves to in the current file. `ssrStatic` is the SSR bundle of a\n * dynamic-mode file: rewritten to the static `useDictionary` (from the\n * package's `/server` entry when it has one — see\n * `SSR_STATIC_IMPORT_SOURCE`) so the server renders static JSON while the\n * client keeps the dynamic loader.\n */\ntype PackageHelperPlan = 'static' | 'dynamic' | 'ssrStatic';\n\n/**\n * Decides, once per package import, which helper family applies to this file.\n * The import rewrite and the per-call rewrite must both derive from this\n * single decision, or the emitted helper and its argument shape diverge.\n *\n * Fetch wins over `ssrStatic`: fetch dictionaries are runtime content, so the\n * server must keep the real fetch path instead of rendering build-time JSON.\n */\nconst resolveHelperPlan = (\n packageName: string,\n importMode: ImportMode | undefined,\n isServer: boolean | undefined,\n packageHasDynamicCall: boolean,\n packageHasFetchCall: boolean\n): PackageHelperPlan => {\n if (!isDynamicPackage(packageName)) return 'static';\n\n if (importMode === 'fetch' || packageHasFetchCall) return 'dynamic';\n\n if (importMode === 'dynamic' || packageHasDynamicCall) {\n return isServer === true ? 'ssrStatic' : 'dynamic';\n }\n\n return 'static';\n};\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()`, `getIntlayer()` and\n * `getIntlayerAsync()` from various Intlayer packages into optimized dictionary\n * access patterns, automatically importing the required dictionary files based\n * 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 / getIntlayerAsync\n * import { getIntlayer, getIntlayerAsync } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * const content = await getIntlayerAsync('app', locale);\n * ```\n *\n * `getIntlayerAsync` is the exception to the mode table below: it always\n * resolves to the per-locale loader (`getDictionaryAsync`), in every import\n * mode, since loading a single locale is what it exists for.\n *\n * **Output (any mode):**\n * ```ts\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { getDictionaryAsync as getIntlayerAsync } from 'intlayer';\n *\n * const content = await getIntlayerAsync(_dicHash_dyn, 'app', locale);\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 * ### Fetch Mode (`importMode = \"fetch\"`)\n *\n * Uses fetch-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"fetch\" 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 \"fetch\" value, the plugin will fallback to the dynamic import mode.\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}): PluginObject<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._callerPackageMap = new Map();\n this._compatCallerMap = new Map();\n this._isIncluded = true;\n this._hasValidImport = false;\n this._isDictEntry = 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 ? normalizePath(this.file.opts.filename)\n : undefined;\n if (this.opts.filesList && filename) {\n const isIncluded = getNormalizedFilesListSet(this.opts.filesList).has(\n filename\n );\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 ? normalizePath(state.file.opts.filename)\n : undefined;\n const dictionariesEntryPath = state.opts.dictionariesEntryPath\n ? normalizePath(state.opts.dictionariesEntryPath)\n : undefined;\n\n // Check if this is the correct file to transform\n\n if (\n state.opts.replaceDictionaryEntry &&\n filename === 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 // Compat callers the optimize pass can rewrite (injected by the\n // compat packages' bundler plugins — nothing is hard-coded here).\n const rewritableCompatCallers = getRewritableCallers(\n state.opts.compatCallers ?? []\n );\n\n // Manual traversal to process imports and call expressions\n // This runs AFTER all other plugins' visitors have completed\n programPath.traverse({\n /* Inspect every intlayer import before deciding helper rewrites. */\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n const isNativePackage = PACKAGE_LIST.includes(src);\n const compatCallersForSource = rewritableCompatCallers.filter(\n (descriptor) => descriptor.importSources.includes(src)\n );\n\n if (!isNativePackage && compatCallersForSource.length === 0) {\n return;\n }\n\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 (isNativePackage && isCallerName(importedName)) {\n state._callerMap?.set(spec.local.name, importedName);\n state._callerPackageMap?.set(spec.local.name, src);\n }\n\n const compatDescriptor = compatCallersForSource.find(\n (descriptor) => descriptor.callerName === importedName\n );\n if (compatDescriptor) {\n state._compatCallerMap?.set(\n spec.local.name,\n compatDescriptor\n );\n }\n }\n },\n });\n\n // Pre-pass to determine if dictionary-level overrides require the\n // dynamic helper in an otherwise static file, and to disable the\n // rewrite for compat callers with unresolvable call sites.\n const packagesWithDynamicCall = new Set<string>();\n const packagesWithFetchCall = new Set<string>();\n let compatHasDynamicCall = false;\n const unresolvableCompatLocalNames = new Set<string>();\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const compatDescriptor = state._compatCallerMap?.get(callee.name);\n if (compatDescriptor) {\n const namespaceMatch = resolveCompatNamespaceMatch(\n t,\n path.node.arguments,\n compatDescriptor\n );\n\n if (!namespaceMatch) {\n // Dynamic/absent namespace: the whole local caller must keep\n // its original implementation — rewriting the import while\n // leaving this call untouched would hand a raw namespace\n // string to the dictionary-accepting helper.\n unresolvableCompatLocalNames.add(callee.name);\n return;\n }\n\n const { dictionaryKey } = splitNamespace(\n namespaceMatch.fullNamespace\n );\n const compatOverrideMode =\n state.opts.dictionaryModeMap?.[dictionaryKey];\n if (\n compatOverrideMode === 'dynamic' ||\n compatOverrideMode === 'fetch'\n ) {\n compatHasDynamicCall = true;\n }\n return;\n }\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (originalImportedName !== 'useIntlayer') return;\n\n const callerPackage = state._callerPackageMap?.get(callee.name);\n if (!callerPackage) return;\n\n const key = getKeyFromArgument(path.node.arguments[0], t);\n if (!key) return;\n\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (dictionaryOverrideMode === 'dynamic') {\n packagesWithDynamicCall.add(callerPackage);\n } else if (dictionaryOverrideMode === 'fetch') {\n packagesWithFetchCall.add(callerPackage);\n }\n },\n });\n\n for (const localName of unresolvableCompatLocalNames) {\n state._compatCallerMap?.delete(localName);\n }\n\n const getHelperPlan = (packageName: string): PackageHelperPlan =>\n resolveHelperPlan(\n packageName,\n state.opts.importMode,\n state.opts.isServer,\n packagesWithDynamicCall.has(packageName),\n packagesWithFetchCall.has(packageName)\n );\n\n // Compat helpers are swapped per file (one import specifier serves\n // every call), so the dynamic decision is file-level: a global\n // dynamic/fetch mode or any per-dictionary override flips all\n // rewritten compat calls to the dynamic helper.\n const compatUseDynamicHelpers =\n state.opts.importMode === 'dynamic' ||\n state.opts.importMode === 'fetch' ||\n compatHasDynamicCall;\n\n programPath.traverse({\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n // Compat caller import rename: point the specifier at the\n // dictionary-accepting helper exported by the compat package\n // (`useTranslation` → `useDictionary`), keeping the local alias\n // so call sites read unchanged. Locals with unresolvable call\n // sites were dropped from the map and keep the original import.\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const compatDescriptor = state._compatCallerMap?.get(\n spec.local.name\n );\n if (!compatDescriptor) continue;\n if (!compatDescriptor.importSources.includes(src)) continue;\n\n spec.imported = t.identifier(\n compatUseDynamicHelpers\n ? compatDescriptor.dynamicReplacement!\n : compatDescriptor.staticReplacement!\n );\n }\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n // Per-import swap, mirrored across bundles — Solid hydration\n // ids rely on the SSR and client helpers consuming one\n // resource slot per call alike (see solid-intlayer/server).\n const helperPlan = getHelperPlan(src);\n const serverSource =\n helperPlan === 'ssrStatic'\n ? SSR_STATIC_IMPORT_SOURCE[src]\n : undefined;\n\n const helperMap: Record<string, string> =\n helperPlan === 'dynamic'\n ? DYNAMIC_HELPER_MAP\n : STATIC_IMPORT_FUNCTION;\n\n const serverSpecifiers: BabelTypes.ImportSpecifier[] = [];\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 (!isCallerName(importedName)) continue;\n\n if (serverSource && importedName === 'useIntlayer') {\n spec.imported = t.identifier('useDictionary');\n serverSpecifiers.push(spec);\n continue;\n }\n\n const newIdentifier = helperMap[importedName];\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 if (serverSpecifiers.length > 0 && serverSource) {\n // Move the helper to the /server entry, keeping any other\n // specifiers (useLocale, …) on the original import.\n path.insertAfter(\n t.importDeclaration(\n serverSpecifiers,\n t.stringLiteral(serverSource)\n )\n );\n path.node.specifiers = path.node.specifiers.filter(\n (spec) =>\n !serverSpecifiers.includes(\n spec as BabelTypes.ImportSpecifier\n )\n );\n if (path.node.specifiers.length === 0) {\n path.remove();\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 // Compat caller rewrite, e.g. (mirroring the SWC plugin):\n // static: useTranslation('about.counter', opts)\n // → useDictionary(_hash, 'counter', opts)\n // dynamic: useTranslation('about.counter', opts)\n // → useDictionaryDynamic(_hash_dyn, 'about', 'counter', opts)\n // option: useI18n({ namespace: 'about' })\n // → useDictionary(_hash, {})\n // fixed: useLingui() → useDictionary(_hash)\n const compatDescriptor = state._compatCallerMap?.get(callee.name);\n if (compatDescriptor) {\n const callArguments = path.node.arguments;\n const namespaceMatch = resolveCompatNamespaceMatch(\n t,\n callArguments,\n compatDescriptor\n );\n if (!namespaceMatch) return; // filtered by the pre-pass — stay safe\n\n state._hasValidImport = true;\n\n const { dictionaryKey, keyPrefix } = splitNamespace(\n namespaceMatch.fullNamespace\n );\n const compatOverrideMode =\n state.opts.dictionaryModeMap?.[dictionaryKey];\n\n // The import specifier serves every call in the file, so a\n // dynamic file receives a dynamic loader for every call.\n let compatCallMode: ImportMode = 'static';\n if (compatUseDynamicHelpers) {\n if (\n compatOverrideMode === 'dynamic' ||\n compatOverrideMode === 'fetch'\n ) {\n compatCallMode = compatOverrideMode;\n } else if (\n state.opts.importMode === 'dynamic' ||\n state.opts.importMode === 'fetch'\n ) {\n compatCallMode = state.opts.importMode;\n } else {\n compatCallMode = 'dynamic';\n }\n }\n\n let dictionaryIdent: BabelTypes.Identifier;\n if (compatCallMode === 'static') {\n let staticIdent = state._newStaticImports?.get(dictionaryKey);\n if (!staticIdent) {\n staticIdent = makeIdent(dictionaryKey, t);\n state._newStaticImports?.set(dictionaryKey, staticIdent);\n }\n dictionaryIdent = staticIdent;\n } else {\n let dynamicIdent =\n state._newDynamicImports?.get(dictionaryKey);\n if (!dynamicIdent) {\n const hash = getPathHash(dictionaryKey);\n const suffix = compatCallMode === 'fetch' ? 'fetch' : 'dyn';\n dynamicIdent = t.identifier(`_${hash}_${suffix}`);\n state._newDynamicImports?.set(dictionaryKey, dynamicIdent);\n }\n dictionaryIdent = dynamicIdent;\n }\n\n if (namespaceMatch.argumentIndex !== undefined) {\n // Positional namespace: replace the string with the\n // dictionary, then (dynamic) key and (nested) prefix.\n callArguments[namespaceMatch.argumentIndex] = t.identifier(\n dictionaryIdent.name\n );\n const insertAt = namespaceMatch.argumentIndex + 1;\n const insertedArguments: BabelTypes.Expression[] = [];\n if (compatCallMode !== 'static') {\n insertedArguments.push(t.stringLiteral(dictionaryKey));\n }\n if (keyPrefix) {\n insertedArguments.push(t.stringLiteral(keyPrefix));\n }\n callArguments.splice(insertAt, 0, ...insertedArguments);\n } else if (compatCallMode !== 'static') {\n // Fixed / option namespace, dynamic helper: prepend the\n // loader and the dictionary key.\n callArguments.unshift(\n t.identifier(dictionaryIdent.name),\n t.stringLiteral(dictionaryKey)\n );\n } else {\n // Fixed / option namespace, static helper: prepend the\n // dictionary object.\n callArguments.unshift(t.identifier(dictionaryIdent.name));\n }\n\n // Option namespace: leave only the key-prefix remainder in the\n // options object (or drop the property for a plain namespace),\n // so the runtime helper does not re-apply the dictionary key\n // as a lookup prefix.\n if (\n namespaceMatch.optionProperty &&\n namespaceMatch.optionsObject\n ) {\n if (keyPrefix) {\n namespaceMatch.optionProperty.value =\n t.stringLiteral(keyPrefix);\n } else {\n namespaceMatch.optionsObject.properties =\n namespaceMatch.optionsObject.properties.filter(\n (property) => property !== namespaceMatch.optionProperty\n );\n }\n }\n\n return;\n }\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 key = getKeyFromArgument(path.node.arguments[0], t);\n if (!key) return;\n\n const callerPackage = state._callerPackageMap?.get(callee.name);\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const isGetIntlayerAsync =\n originalImportedName === 'getIntlayerAsync';\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n const helperPlan =\n callerPackage === undefined\n ? 'static'\n : getHelperPlan(callerPackage);\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'.\n let perCallMode: ImportMode = 'static';\n\n if (isGetIntlayerAsync) {\n // Loading a single locale is the whole point of the async\n // getter, so it reads a per-locale loader whatever the file's\n // import mode is — the fetch loader when the dictionary is\n // remote, the dynamic one otherwise.\n perCallMode =\n dictionaryOverrideMode === 'fetch' ? 'fetch' : 'dynamic';\n } else if (isUseIntlayer && helperPlan === 'dynamic') {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic' || importMode === 'fetch') {\n perCallMode = importMode;\n }\n } else if (isUseIntlayer && helperPlan === 'static') {\n // The global mode is static, but a per-dictionary override can\n // still force dynamic/fetch for this specific call.\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 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 const nestingDictionaryKeys = new Set(\n state.opts.nestingDictionaryKeys ?? []\n );\n\n // Generate static JSON imports (getIntlayer always uses JSON dictionaries)\n for (const [key, ident] of state._newStaticImports!) {\n // Dictionaries holding `nest()` references are imported through\n // their companion module, which re-exports them with the nest\n // targets attached.\n const hasNestedDictionaries = nestingDictionaryKeys.has(key);\n\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static',\n hasNestedDictionaries\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 if (!hasNestedDictionaries) {\n importDeclarationNode.attributes = [\n t.importAttribute(\n t.identifier('type'),\n t.stringLiteral('json')\n ),\n ];\n }\n\n imports.push(importDeclarationNode);\n }\n\n // Generate dynamic/fetch imports (for useIntlayer when using dynamic/fetch 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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"}
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 { PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type { CallerDescriptor } from '@intlayer/config/callers';\nimport { normalizePath } from '@intlayer/config/utils';\nimport { createCompatOptimizePass } from './compat/optimizePass';\nimport {\n createDictionaryImportRegistry,\n type DictionaryImportRegistry,\n type ImportMode,\n} from './dictionaryImports';\nimport { getNormalizedFilesListSet } from './normalizedFilesList';\nimport { readStaticString } from './staticAstReaders';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n '@intlayer/core/interpreter',\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', 'getIntlayerAsync'] as const;\n\n/** Membership sets for the tables above — hit once per import and per call. */\nconst NATIVE_PACKAGE_SET: ReadonlySet<string> = new Set(PACKAGE_LIST);\nconst CALLER_NAME_SET: ReadonlySet<string> = new Set(CALLER_LIST);\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 DYNAMIC_PACKAGE_SET: ReadonlySet<string> = new Set(PACKAGE_LIST_DYNAMIC);\n\nconst STATIC_IMPORT_FUNCTION = {\n getIntlayer: 'getDictionary',\n // `getIntlayerAsync` always reads a per-locale chunk, in every import mode,\n // so its helper is the same on both sides of the static/dynamic split.\n getIntlayerAsync: 'getDictionaryAsync',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\n/** Import rename table applied when the helper plan for a package is `dynamic`. */\nconst DYNAMIC_HELPER_MAP: Record<string, string> = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n};\n\n/**\n * Packages whose SSR-static `useDictionary` lives in a `/server` subpath\n * because it differs from the root one. Solid's reserves one hydration\n * resource slot so hydration ids stay aligned with the client's\n * `useDictionaryDynamic`; for other frameworks the root `useDictionary` is\n * already the correct SSR-static implementation.\n */\nconst SSR_STATIC_IMPORT_SOURCE: Partial<Record<string, string>> = {\n 'solid-intlayer': 'solid-intlayer/server',\n};\n\ntype CallerName = (typeof CALLER_LIST)[number];\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 * Keys of the dictionaries that reference other dictionaries through\n * `nest()`.\n *\n * For those, the injected static import points at the generated companion\n * module (`<dictionariesDir>/nested/<key>.mjs`) instead of the raw JSON. The\n * companion re-exports the dictionary with its nest targets attached, so\n * `getNesting` resolves them from that local reference rather than from the\n * global registry this plugin empties — and each target lands in the chunk of\n * the dictionary referencing it.\n *\n * Dynamic and fetch modes need nothing here: their generated loaders already\n * attach the same targets per locale.\n */\n nestingDictionaryKeys?: string[];\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 * Whether the current transform is for an SSR bundle.\n */\n isServer?: boolean;\n /**\n * Compat-adapter caller descriptors injected by the compat packages'\n * bundler plugins (e.g. `@intlayer/react-i18next/plugin`).\n *\n * Callers carrying `staticReplacement` / `dynamicReplacement` are rewritten\n * the same way as native `useIntlayer` calls: the namespace string argument\n * is replaced by a pre-imported dictionary object and the import specifier\n * is re-pointed to the dictionary-accepting variant, e.g.\n * `useTranslation('about')` → `useDictionary(_dictHash)`.\n *\n * No compat-specific name is hard-coded in this plugin — the whole rewrite\n * is driven by these descriptors.\n */\n compatCallers?: CallerDescriptor[];\n};\n\n/**\n * Per-file plugin state.\n *\n * Only the two flags that have to survive from `pre` / `Program.enter` into\n * `Program.exit` live here — the caller maps and the import registry are built\n * and consumed inside `exit`, so they stay local to it.\n */\ntype State = PluginPass & {\n opts: OptimizePluginOptions;\n /** Whether the current file *is* a generated dictionaries entry file. */\n _isDictEntry?: boolean;\n /** Whether the current file is covered by the `filesList` allowlist. */\n _isIncluded?: boolean;\n};\n\n/**\n * Builds the module specifier pointing at `targetPath` from `fromFile`, with\n * forward slashes and an explicit `./` prefix so bundlers never treat it as a\n * bare package specifier.\n */\nconst toRelativeSpecifier = (fromFile: string, targetPath: string): string => {\n // Fix windows path\n const relativePath = normalizePath(relative(dirname(fromFile), targetPath));\n\n // Fix relative path\n if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {\n return `./${relativePath}`;\n }\n\n return relativePath;\n};\n\n/**\n * Subdirectory of the compiled dictionaries holding the companion modules that\n * re-export a dictionary with its `nest()` targets attached. Mirrors\n * `NESTED_DICTIONARIES_SUBDIR` in `@intlayer/engine`.\n */\nconst NESTED_DICTIONARIES_SUBDIR = 'nested';\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch',\n hasNestedDictionaries = false\n): string => {\n // Static mode is the only one needing the companion: the dynamic and fetch\n // loaders already attach the nest targets per locale.\n let dictionaryPath = hasNestedDictionaries\n ? join(dictionariesDir, NESTED_DICTIONARIES_SUBDIR, `${key}.mjs`)\n : join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n dictionaryPath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n dictionaryPath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n return toRelativeSpecifier(fromFile, dictionaryPath);\n};\n\nconst isCallerName = (name: string): name is CallerName =>\n CALLER_NAME_SET.has(name);\n\nconst isDynamicPackage = (packageName: string): boolean =>\n DYNAMIC_PACKAGE_SET.has(packageName);\n\n/** The name an import specifier brings in, whether written as an identifier or a string. */\nconst importedSpecifierName = (\n babelTypes: typeof BabelTypes,\n specifier: BabelTypes.ImportSpecifier\n): string =>\n babelTypes.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value;\n\nconst EMPTY_KEY_SET: ReadonlySet<string> = new Set<string>();\n\n/**\n * `nestingDictionaryKeys` as a set, cached by array reference: Babel reuses one\n * options object for the whole build, so the set is built once instead of once\n * per transformed file.\n */\nconst nestingKeysCache = new WeakMap<readonly string[], ReadonlySet<string>>();\n\nconst getNestingDictionaryKeySet = (\n nestingDictionaryKeys: readonly string[] | undefined\n): ReadonlySet<string> => {\n if (!nestingDictionaryKeys) return EMPTY_KEY_SET;\n\n let keySet = nestingKeysCache.get(nestingDictionaryKeys);\n\n if (!keySet) {\n keySet = new Set(nestingDictionaryKeys);\n nestingKeysCache.set(nestingDictionaryKeys, keySet);\n }\n\n return keySet;\n};\n\n/**\n * Helper family every `useIntlayer`/`getIntlayer` call from one package import\n * resolves to in the current file. `ssrStatic` is the SSR bundle of a\n * dynamic-mode file: rewritten to the static `useDictionary` (from the\n * package's `/server` entry when it has one — see\n * `SSR_STATIC_IMPORT_SOURCE`) so the server renders static JSON while the\n * client keeps the dynamic loader.\n */\ntype PackageHelperPlan = 'static' | 'dynamic' | 'ssrStatic';\n\n/**\n * Decides, once per package import, which helper family applies to this file.\n * The import rewrite and the per-call rewrite must both derive from this\n * single decision, or the emitted helper and its argument shape diverge.\n *\n * Fetch wins over `ssrStatic`: fetch dictionaries are runtime content, so the\n * server must keep the real fetch path instead of rendering build-time JSON.\n */\nconst resolveHelperPlan = (\n packageName: string,\n importMode: ImportMode | undefined,\n isServer: boolean | undefined,\n packageHasDynamicCall: boolean,\n packageHasFetchCall: boolean\n): PackageHelperPlan => {\n if (!isDynamicPackage(packageName)) return 'static';\n\n if (importMode === 'fetch' || packageHasFetchCall) return 'dynamic';\n\n if (importMode === 'dynamic' || packageHasDynamicCall) {\n return isServer === true ? 'ssrStatic' : 'dynamic';\n }\n\n return 'static';\n};\n\n/**\n * Builds the dictionary import declarations the rewrite accumulated for one\n * file — the compiled JSON (or its `nest()` companion) for static reads, and\n * the generated per-locale loader for dynamic and fetch ones.\n */\nconst buildDictionaryImportDeclarations = (\n babelTypes: typeof BabelTypes,\n state: State,\n imports: DictionaryImportRegistry\n): BabelTypes.ImportDeclaration[] => {\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n nestingDictionaryKeys,\n } = state.opts;\n const fromFile = state.file.opts.filename!;\n const nestingKeys = getNestingDictionaryKeySet(nestingDictionaryKeys);\n\n const importDeclarations: BabelTypes.ImportDeclaration[] = [];\n\n // Static JSON imports — `getIntlayer` always reads a JSON dictionary.\n for (const [key, ident] of imports.staticImports) {\n // A dictionary holding `nest()` references is imported through its\n // companion module, which re-exports it with the nest targets attached.\n const hasNestedDictionaries = nestingKeys.has(key);\n\n const specifier = computeImport(\n fromFile,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static',\n hasNestedDictionaries\n );\n\n const importDeclaration = babelTypes.importDeclaration(\n [babelTypes.importDefaultSpecifier(babelTypes.identifier(ident.name))],\n babelTypes.stringLiteral(specifier)\n );\n\n if (!hasNestedDictionaries) {\n importDeclaration.attributes = [\n babelTypes.importAttribute(\n babelTypes.identifier('type'),\n babelTypes.stringLiteral('json')\n ),\n ];\n }\n\n importDeclarations.push(importDeclaration);\n }\n\n // Per-locale loaders — `useIntlayer` under a dynamic or fetch helper.\n for (const [key, ident] of imports.dynamicImports) {\n const mode: ImportMode = ident.name.endsWith('_fetch')\n ? 'fetch'\n : 'dynamic';\n\n importDeclarations.push(\n babelTypes.importDeclaration(\n [babelTypes.importDefaultSpecifier(babelTypes.identifier(ident.name))],\n babelTypes.stringLiteral(\n computeImport(\n fromFile,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n mode\n )\n )\n )\n );\n }\n\n return importDeclarations;\n};\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()`, `getIntlayer()` and\n * `getIntlayerAsync()` from various Intlayer packages into optimized dictionary\n * access patterns, automatically importing the required dictionary files based\n * 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 / getIntlayerAsync\n * import { getIntlayer, getIntlayerAsync } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * const content = await getIntlayerAsync('app', locale);\n * ```\n *\n * `getIntlayerAsync` is the exception to the mode table below: it always\n * resolves to the per-locale loader (`getDictionaryAsync`), in every import\n * mode, since loading a single locale is what it exists for.\n *\n * **Output (any mode):**\n * ```ts\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { getDictionaryAsync as getIntlayerAsync } from 'intlayer';\n *\n * const content = await getIntlayerAsync(_dicHash_dyn, 'app', locale);\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 * ### Fetch Mode (`importMode = \"fetch\"`)\n *\n * Uses fetch-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"fetch\" 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 \"fetch\" value, the plugin will fallback to the dynamic import mode.\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}): PluginObject<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-transform',\n\n pre() {\n this._isIncluded = true;\n this._isDictEntry = 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 ? normalizePath(this.file.opts.filename)\n : undefined;\n if (this.opts.filesList && filename) {\n const isIncluded = getNormalizedFilesListSet(this.opts.filesList).has(\n filename\n );\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 ? normalizePath(state.file.opts.filename)\n : undefined;\n const dictionariesEntryPath = state.opts.dictionariesEntryPath\n ? normalizePath(state.opts.dictionariesEntryPath)\n : undefined;\n\n // If this file *is* the dictionaries entry, short-circuit: export {}\n // Note: unmerged_dictionaries.mjs is used by the visual editor and CMS\n // and must not be replaced with an empty dictionary map.\n if (\n state.opts.replaceDictionaryEntry &&\n filename &&\n filename === dictionariesEntryPath\n ) {\n state._isDictEntry = true;\n programPath.node.body = [\n t.exportDefaultDeclaration(t.objectExpression([])),\n t.exportNamedDeclaration(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier('getDictionaries'),\n t.arrowFunctionExpression([], t.objectExpression([]))\n ),\n ])\n ),\n ];\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 const imports = createDictionaryImportRegistry(t);\n\n // Compat adapters plug in here and nowhere else: with no descriptor\n // injected the pass is `null`, every `compat?.` below is a no-op and\n // the native rewrite runs exactly as if the adapters did not exist.\n const compat = createCompatOptimizePass(\n t,\n state.opts.compatCallers ?? [],\n imports,\n {\n importMode: state.opts.importMode,\n dictionaryModeMap: state.opts.dictionaryModeMap,\n }\n );\n\n // ── Step 1 — imports.\n //\n // An import declaration is only ever a direct child of Program, so\n // the body is scanned instead of walking the whole AST, and the\n // result is complete before any call site is inspected (a file may\n // import below the calls it governs).\n const programBody = programPath.node.body;\n const nativeImportNodes: BabelTypes.ImportDeclaration[] = [];\n const compatImportNodes: BabelTypes.ImportDeclaration[] = [];\n /** Local alias → the native caller it was imported as. */\n const callerMap = new Map<string, CallerName>();\n /** Local alias → the intlayer package it was imported from. */\n const callerPackageMap = new Map<string, string>();\n /** Whether the file imported any native or compat caller at all. */\n let hasValidImport = false;\n\n for (const statement of programBody) {\n if (!t.isImportDeclaration(statement)) continue;\n\n const src = statement.source.value;\n const isNativePackage = NATIVE_PACKAGE_SET.has(src);\n const isCompatPackage = compat?.ownsImportSource(src) ?? false;\n\n if (!isNativePackage && !isCompatPackage) continue;\n\n hasValidImport = true;\n if (isNativePackage) nativeImportNodes.push(statement);\n if (isCompatPackage) compatImportNodes.push(statement);\n\n for (const spec of statement.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = importedSpecifierName(t, spec);\n\n if (isNativePackage && isCallerName(importedName)) {\n callerMap.set(spec.local.name, importedName);\n callerPackageMap.set(spec.local.name, src);\n }\n\n if (isCompatPackage) {\n compat?.noteImport(src, importedName, spec.local.name);\n }\n }\n }\n\n // ── Step 2 — the file's only AST walk, collecting the call sites\n // that either half of the rewrite can act on. Everything after\n // this point works on the collected arrays.\n const nativeCallNodes: BabelTypes.CallExpression[] = [];\n const compatCallNodes: BabelTypes.CallExpression[] = [];\n\n if (callerMap.size > 0 || compat?.hasCallers()) {\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n if (!t.isIdentifier(callee)) return;\n\n if (callerMap.has(callee.name)) {\n nativeCallNodes.push(path.node);\n } else if (compat?.ownsLocalName(callee.name)) {\n compatCallNodes.push(path.node);\n }\n },\n });\n }\n\n // ── Step 3 — analysis.\n //\n // A per-dictionary override reached from this file can promote a\n // whole package import to a dynamic loader, so the helper family is\n // decided per package before anything is rewritten.\n const packagesWithDynamicCall = new Set<string>();\n const packagesWithFetchCall = new Set<string>();\n\n for (const callNode of nativeCallNodes) {\n const callee = callNode.callee as BabelTypes.Identifier;\n if (callerMap.get(callee.name) !== 'useIntlayer') continue;\n\n const callerPackage = callerPackageMap.get(callee.name);\n if (!callerPackage) continue;\n\n const key = readStaticString(t, callNode.arguments[0]);\n if (!key) continue;\n\n const overrideMode = state.opts.dictionaryModeMap?.[key];\n\n if (overrideMode === 'dynamic') {\n packagesWithDynamicCall.add(callerPackage);\n } else if (overrideMode === 'fetch') {\n packagesWithFetchCall.add(callerPackage);\n }\n }\n\n // The compat half decides its own file-level helper family and drops\n // the callers whose call sites it cannot resolve.\n compat?.analyzeCalls(compatCallNodes);\n\n const helperPlanCache = new Map<string, PackageHelperPlan>();\n\n const getHelperPlan = (packageName: string): PackageHelperPlan => {\n let plan = helperPlanCache.get(packageName);\n\n if (plan === undefined) {\n plan = resolveHelperPlan(\n packageName,\n state.opts.importMode,\n state.opts.isServer,\n packagesWithDynamicCall.has(packageName),\n packagesWithFetchCall.has(packageName)\n );\n helperPlanCache.set(packageName, plan);\n }\n\n return plan;\n };\n\n // ── Step 4 — rewrite the imports.\n //\n // Moving a helper to a package's `/server` entry is the only edit\n // that changes the program body; the new declarations are recorded\n // here and applied with the dictionary imports in step 6, so no\n // stored node is invalidated mid-rewrite.\n const serverImportsByAnchor = new Map<\n BabelTypes.ImportDeclaration,\n BabelTypes.ImportDeclaration\n >();\n const emptiedImportNodes = new Set<BabelTypes.ImportDeclaration>();\n\n for (const importNode of compatImportNodes) {\n // Compat caller import rename: point the specifier at the\n // dictionary-accepting helper exported by the compat package\n // (`useTranslation` → `useDictionary`), keeping the local alias so\n // call sites read unchanged.\n compat?.rewriteImportSpecifiers(importNode);\n }\n\n for (const importNode of nativeImportNodes) {\n // Per-import swap, mirrored across bundles — Solid hydration ids\n // rely on the SSR and client helpers consuming one resource slot\n // per call alike (see solid-intlayer/server).\n const helperPlan = getHelperPlan(importNode.source.value);\n const serverSource =\n helperPlan === 'ssrStatic'\n ? SSR_STATIC_IMPORT_SOURCE[importNode.source.value]\n : undefined;\n\n const helperMap: Record<string, string> =\n helperPlan === 'dynamic'\n ? DYNAMIC_HELPER_MAP\n : STATIC_IMPORT_FUNCTION;\n\n const serverSpecifiers: BabelTypes.ImportSpecifier[] = [];\n\n for (const spec of importNode.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = importedSpecifierName(t, spec);\n if (!isCallerName(importedName)) continue;\n\n if (serverSource && importedName === 'useIntlayer') {\n spec.imported = t.identifier('useDictionary');\n serverSpecifiers.push(spec);\n continue;\n }\n\n const newIdentifier = helperMap[importedName];\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 if (serverSpecifiers.length > 0 && serverSource) {\n // Move the helper to the /server entry, keeping any other\n // specifiers (useLocale, …) on the original import.\n serverImportsByAnchor.set(\n importNode,\n t.importDeclaration(\n serverSpecifiers,\n t.stringLiteral(serverSource)\n )\n );\n\n const serverSpecifierSet = new Set<BabelTypes.Node>(\n serverSpecifiers\n );\n importNode.specifiers = importNode.specifiers.filter(\n (spec) => !serverSpecifierSet.has(spec)\n );\n\n if (importNode.specifiers.length === 0) {\n emptiedImportNodes.add(importNode);\n }\n }\n }\n\n // ── Step 5 — rewrite the call sites.\n for (const callNode of compatCallNodes) {\n compat?.rewriteCall(callNode);\n }\n\n for (const callNode of nativeCallNodes) {\n const callee = callNode.callee as BabelTypes.Identifier;\n const originalImportedName = callerMap.get(callee.name)!;\n\n const key = readStaticString(t, callNode.arguments[0]);\n if (!key) continue;\n\n const callerPackage = callerPackageMap.get(callee.name);\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const isGetIntlayerAsync =\n originalImportedName === 'getIntlayerAsync';\n const dictionaryOverrideMode = state.opts.dictionaryModeMap?.[key];\n const helperPlan =\n callerPackage === undefined\n ? 'static'\n : getHelperPlan(callerPackage);\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'.\n let perCallMode: ImportMode = 'static';\n\n if (isGetIntlayerAsync) {\n // Loading a single locale is the whole point of the async\n // getter, so it reads a per-locale loader whatever the file's\n // import mode is — the fetch loader when the dictionary is\n // remote, the dynamic one otherwise.\n perCallMode =\n dictionaryOverrideMode === 'fetch' ? 'fetch' : 'dynamic';\n } else if (isUseIntlayer && helperPlan === 'dynamic') {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic' || importMode === 'fetch') {\n perCallMode = importMode;\n }\n } else if (isUseIntlayer && helperPlan === 'static') {\n // The global mode is static, but a per-dictionary override can\n // still force dynamic/fetch for this specific call.\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n perCallMode = dictionaryOverrideMode;\n }\n }\n\n const ident = imports.identFor(key, perCallMode);\n\n if (perCallMode === 'static') {\n // Static helper (useDictionary / getDictionary): replace the key\n // argument with the imported dictionary object.\n callNode.arguments[0] = t.identifier(ident.name);\n } else {\n // Dynamic / fetch helper: first argument is the loader, the key\n // stays as the second one.\n callNode.arguments = [\n t.identifier(ident.name),\n ...callNode.arguments,\n ];\n }\n }\n\n // ── Step 6 — apply every body edit in one pass.\n const dictionaryImports = hasValidImport\n ? buildDictionaryImportDeclarations(t, state, imports)\n : [];\n\n const hasImportMoves =\n serverImportsByAnchor.size > 0 || emptiedImportNodes.size > 0;\n\n if (dictionaryImports.length === 0 && !hasImportMoves) return;\n\n const nextBody: BabelTypes.Statement[] = [];\n\n for (const statement of programBody) {\n if (\n t.isImportDeclaration(statement) &&\n emptiedImportNodes.has(statement)\n ) {\n // Every specifier moved to the /server entry; drop the husk.\n const serverImport = serverImportsByAnchor.get(statement);\n if (serverImport) nextBody.push(serverImport);\n continue;\n }\n\n nextBody.push(statement);\n\n if (t.isImportDeclaration(statement)) {\n const serverImport = serverImportsByAnchor.get(statement);\n if (serverImport) nextBody.push(serverImport);\n }\n }\n\n /* Keep \"use client\" / \"use server\" directives at the very top. */\n let insertPos = 0;\n for (const statement of nextBody) {\n if (\n t.isExpressionStatement(statement) &&\n t.isStringLiteral(statement.expression) &&\n !statement.expression.value.startsWith('import') &&\n !statement.expression.value.startsWith('require')\n ) {\n insertPos += 1;\n } else {\n break;\n }\n }\n\n nextBody.splice(insertPos, 0, ...dictionaryImports);\n programPath.node.body = nextBody;\n },\n },\n },\n };\n};\n"],"mappings":"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"}
@@ -1,11 +1,12 @@
1
- import { createPruneContext, makeUsageAnalyzerBabelPlugin, preserveNestedDictionaryFields } from "./babel-plugin-intlayer-usage-analyzer.mjs";
1
+ import { createPruneContext, preserveNestedDictionaryFields } from "./pruneContext.mjs";
2
+ import { makeUsageAnalyzerBabelPlugin } from "./babel-plugin-intlayer-usage-analyzer.mjs";
2
3
  import { extractScriptBlocks } from "./extractScriptBlocks.mjs";
3
4
  import { buildNestedRenameMapFromContent, getNestedRenameEntryAtPath } from "./babel-plugin-intlayer-field-rename.mjs";
4
5
  import { BABEL_PARSER_OPTIONS, SOURCE_FILE_REGEX, getUsageCheckRegex } from "./transformers.mjs";
5
6
  import { join } from "node:path";
7
+ import { formatPath } from "@intlayer/engine/utils";
6
8
  import * as ANSIColors from "@intlayer/config/colors";
7
9
  import { colorize, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
8
- import { formatPath } from "@intlayer/engine/utils";
9
10
  import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
10
11
  import { transformSync } from "@babel/core";
11
12
 
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-purge.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-purge.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { PluginObject, PluginPass } from '@babel/core';\nimport { transformSync } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\nimport { formatPath } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n buildNestedRenameMapFromContent,\n getNestedRenameEntryAtPath,\n} from './babel-plugin-intlayer-field-rename';\nimport {\n type CompatCallerConfig,\n createPruneContext,\n makeUsageAnalyzerBabelPlugin,\n type NestedRenameMap,\n type PruneContext,\n preserveNestedDictionaryFields,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks } from './extractScriptBlocks';\nimport {\n BABEL_PARSER_OPTIONS,\n getUsageCheckRegex,\n SOURCE_FILE_REGEX,\n} from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerPurgeBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getPurgePluginOptions}) so the plugin itself does not need to read\n * the configuration file on every file transform.\n */\nexport type PurgePluginOptions = {\n /**\n * Absolute path to the project root. Used as the cache key for the shared\n * {@link PruneContext} so two Babel transform pipelines for different\n * workspaces in the same process do not share state.\n */\n baseDir: string;\n\n /**\n * When `true`, remove unused content fields from compiled dictionary JSON\n * files. Mirrors `build.purge` in `intlayer.config.ts`.\n */\n purge: boolean;\n\n /**\n * When `true`, rename content fields to short alphabetic aliases\n * (`title` → `a`, etc.) and strip top-level metadata from compiled\n * dictionaries. Mirrors `build.minify` in `intlayer.config.ts`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. `undefined` means \"auto\" (active for\n * production builds). When explicitly `false`, the plugin is a no-op.\n * Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin still purges and minifies, but skips field renaming\n * so the `keyPath` reported to the visual editor keeps matching the unmerged\n * dictionaries it edits. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n\n /**\n * Absolute path to the compiled static dictionaries directory\n * (`.intlayer/dictionaries/` by default).\n */\n dictionariesDir: string;\n\n /**\n * Absolute path to the compiled per-locale dynamic dictionaries directory\n * (`.intlayer/dynamic_dictionaries/` by default).\n */\n dynamicDictionariesDir: string;\n\n /**\n * Pre-built list of component source file paths to analyse for field-usage.\n * Populated by {@link getPurgePluginOptions} from the intlayer config's\n * `content` glob patterns.\n */\n componentFilesList: string[];\n\n /**\n * Dictionaries referenced through `nest()`, mapped to the top-level fields\n * those references read (`'all'` when the whole dictionary is nested).\n *\n * Those fields are resolved at runtime by `getNesting` using their original\n * names, so they must survive purging and must never be renamed.\n */\n nestedDictionaryReferences?: Map<string, NestedFieldReferences>;\n\n /**\n * Per-dictionary import-mode overrides, keyed by dictionary `key`.\n * Dictionaries with mode `'fetch'` are excluded from field renaming because\n * their JSON is served from a remote API using the original field names.\n */\n dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n\n /**\n * Compat-adapter namespace caller configurations.\n *\n * When set, the usage analyser recognises these additional translation\n * function patterns and maps them to dictionary field usage. Each compat\n * adapter package (e.g. `@intlayer/react-i18next`) provides its own caller\n * list; they are NOT hardcoded here so that `@intlayer/babel` stays\n * framework-agnostic.\n *\n * Defaults to `[]` (no compat callers) when omitted.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Logging configuration used to report what the pipeline purged and\n * minified, so a Babel- or SWC-driven build prints the same summary as the\n * Vite one. Mirrors `log` in `intlayer.config.ts`; omit to fall back to the\n * logger defaults.\n */\n logConfig?: Pick<IntlayerConfig, 'log'>;\n};\n\n/** Application logger used to report the pipeline's progress. */\ntype PurgeLogger = ReturnType<typeof getAppLogger>;\n\n// ── Shared module-level state ─────────────────────────────────────────────────\n\n/**\n * Cache of built {@link PruneContext} objects, keyed by the project's\n * `baseDir`. Each context is built exactly once per Node.js process:\n * {@link runIntlayerPurgePipeline} registers the context before running so every\n * subsequent transform is a no-op cache hit.\n */\nconst _pruneContextCache = new Map<string, PruneContext>();\n\n/**\n * Returns the shared {@link PruneContext} for the given base directory, or\n * `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for\n * that directory.\n *\n * Used by {@link intlayerMinifyBabelPlugin} to read the rename map without\n * creating a circular dependency.\n */\nexport const getSharedPruneContext = (baseDir: string): PruneContext | null =>\n _pruneContextCache.get(baseDir) ?? null;\n\n// ── Dictionary JSON types ─────────────────────────────────────────────────────\n\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string;\n [extraKey: string]: unknown;\n};\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// ── Prune helpers (mirrors intlayerPrunePlugin) ───────────────────────────────\n\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/** Returns a copy of `record` keeping only the keys listed in `usedFieldNames`. */\nconst filterRecordByUsedFields = (\n record: Record<string, unknown>,\n usedFieldNames: Set<string>\n): Record<string, unknown> => {\n const filteredRecord: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(record)) {\n if (usedFieldNames.has(fieldName)) {\n filteredRecord[fieldName] = fieldValue;\n }\n }\n return filteredRecord;\n};\n\n/**\n * Removes unused fields from a **static** dictionary (all locales in one\n * file). Supports shape A (translation node at the root) and shape B (flat\n * record of translation nodes per field).\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A: { nodeType: \"translation\", translation: { en: { f1, f2 } } }\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n if (isPlainRecord(firstLocaleValue)) {\n const prunedTranslation: Record<string, unknown> = {};\n for (const [locale, localeContent] of Object.entries(\n content.translation\n )) {\n prunedTranslation[locale] = isPlainRecord(localeContent)\n ? filterRecordByUsedFields(localeContent, usedFieldNames)\n : localeContent;\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslation },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B: { field1: { nodeType: \"translation\", … }, field2: { … } }\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Removes unused fields from a **dynamic / per-locale** dictionary file\n * (one JSON per locale, flat `content` record).\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n};\n\n// ── Minify helpers (mirrors intlayerMinifyPlugin) ─────────────────────────────\n\n/**\n * Recursively renames user-defined content fields using `renameMap`.\n * Translation nodes, arrays, and primitives follow the same traversal rules\n * as in the Vite-based minify plugin.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale value with the same map.\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values.\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val;\n }\n }\n return result;\n};\n\n/**\n * Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming\n * only the keys inside `content` while leaving top-level metadata untouched.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// ── Synchronous source-file analysis ─────────────────────────────────────────\n\n/**\n * Runs the usage-analyser Babel plugin synchronously on a single code block,\n * accumulating results into `pruneContext`.\n */\nconst analyzeCodeBlockSync = (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n try {\n transformSync(code, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false,\n });\n } catch {\n pruneContext.hasUnparsableSourceFiles = true;\n }\n};\n\n/**\n * Reads a source file from disk and runs the usage-analyser synchronously.\n * SFC files (Vue / Svelte) are handled by extracting script blocks first.\n */\nconst analyzeSourceFileSync = (\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n let code: string;\n try {\n code = readFileSync(sourceFilePath, 'utf-8');\n } catch {\n return;\n }\n\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n\n if (!usageCheckRegex.test(code)) return;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n analyzeCodeBlockSync(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n// ── Build rename maps ─────────────────────────────────────────────────────────\n\n/**\n * Reads compiled dictionary JSON files to build the nested field-rename maps,\n * mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's\n * `buildStart` hook. Results are stored in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n */\nconst buildRenameMapsSynchronously = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n dictionaryKeyToImportModeMap: PurgePluginOptions['dictionaryKeyToImportModeMap'],\n pruneContext: PruneContext,\n logger: PurgeLogger\n): void => {\n let partiallyMinifiedDictionariesCount = 0;\n\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch') continue;\n if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey))\n continue;\n\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);\n if (existsSync(staticJsonPath)) {\n try {\n const raw = readFileSync(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n // Fall through to dynamic dict.\n }\n }\n\n if (!dictionaryContent) {\n const dynamicDirPath = join(dynamicDictionariesDir, dictionaryKey);\n if (existsSync(dynamicDirPath)) {\n try {\n const localeFiles = readdirSync(dynamicDirPath);\n const firstJsonFile = localeFiles.find((file) =>\n file.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = readFileSync(\n join(dynamicDirPath, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary not readable – skip rename for this key.\n }\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent);\n\n // Preserve children of opaque values to avoid breaking child components.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n if (opaqueFieldMap) {\n const dangerousOccurrences = [...opaqueFieldMap.values()].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(nestedRenameMap, occurrence.fieldPath)\n ?.children.size ?? 0) > 0\n );\n\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(`'${occurrence.fieldPath.join('.')}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (location) => `\\n at ${formatPath(location)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n for (const occurrence of dangerousOccurrences) {\n const renameEntry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (renameEntry) {\n renameEntry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n }\n};\n\n// ── Dictionary file writing ───────────────────────────────────────────────────\n\n/**\n * Names of the top-level content fields of a compiled dictionary, whatever\n * shape its `content` uses. Used to report which fields the purge removed.\n */\nconst readContentFieldNames = (\n content: CompiledDictionaryJson['content']\n): string[] => {\n // Shape A – fields live inside each locale object.\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n return isPlainRecord(firstLocaleValue) ? Object.keys(firstLocaleValue) : [];\n }\n\n // Shape B / dynamic – flat content record.\n if (isPlainRecord(content)) return Object.keys(content);\n\n return [];\n};\n\n/**\n * Running totals of the purge, reported once the whole pipeline is done.\n */\ntype PurgeStats = {\n /** Dictionary key → number of fields removed from it. */\n prunedFieldsCountPerDictionary: Map<string, number>;\n /**\n * Dictionary keys whose \"pruned fields\" line has already been printed. A\n * key spans several files (one static, one per locale), and they all remove\n * the same fields.\n */\n loggedPrunedDictionaryKeys: Set<string>;\n /**\n * Dictionary keys whose \"analysis is incomplete\" warning has already been\n * printed, deduplicated the same way.\n */\n loggedIncompleteAnalysisKeys: Set<string>;\n};\n\n/**\n * Reads a compiled dictionary JSON file, applies the purge and/or minify\n * transformations, and writes it back in-place when something changed.\n *\n * `dictionaryKind` selects the prune strategy and the minified output shape:\n * static dictionaries keep `{ key, content }`, dynamic per-locale\n * dictionaries additionally keep `locale`.\n */\nconst processDictionaryFile = (\n filePath: string,\n dictionaryKind: 'static' | 'dynamic',\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger,\n stats: PurgeStats\n): void => {\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(\n readFileSync(filePath, 'utf-8')\n ) as CompiledDictionaryJson;\n } catch {\n return; // Unreadable or invalid JSON – leave file unchanged.\n }\n\n const { key: dictionaryKey } = parsedDict;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(filePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const pruneDictionaryContent =\n dictionaryKind === 'static'\n ? pruneStaticDictionaryContent\n : pruneDynamicDictionaryContent;\n const originalFieldNames = readContentFieldNames(parsedDict.content);\n const { prunedDictionary, wasRecognised } = pruneDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(filePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n stats.prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!stats.loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n stats.loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n parsedDict = prunedDictionary;\n modified = true;\n } else if (\n !fieldUsage &&\n pruneContext.hasUnparsableSourceFiles &&\n !stats.loggedIncompleteAnalysisKeys.has(dictionaryKey)\n ) {\n // An unparsable source file might be the one referencing this key, so its\n // usage set cannot be trusted to be empty.\n stats.loggedIncompleteAnalysisKeys.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? dictionaryKind === 'static'\n ? { key: parsedDict.key, content: parsedDict.content }\n : {\n key: parsedDict.key,\n content: parsedDict.content,\n locale: parsedDict.locale,\n }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processAllDictionaryFiles = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger\n): void => {\n const stats: PurgeStats = {\n prunedFieldsCountPerDictionary: new Map(),\n loggedPrunedDictionaryKeys: new Set(),\n loggedIncompleteAnalysisKeys: new Set(),\n };\n\n if (existsSync(dictionariesDir)) {\n for (const entry of readdirSync(dictionariesDir)) {\n if (!entry.endsWith('.json')) continue;\n processDictionaryFile(\n join(dictionariesDir, entry),\n 'static',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n }\n\n if (existsSync(dynamicDictionariesDir)) {\n for (const keyDir of readdirSync(dynamicDictionariesDir)) {\n const keyDirPath = join(dynamicDictionariesDir, keyDir);\n try {\n for (const localeFile of readdirSync(keyDirPath)) {\n if (!localeFile.endsWith('.json')) continue;\n processDictionaryFile(\n join(keyDirPath, localeFile),\n 'dynamic',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n } catch {\n // Unreadable key directory – skip.\n }\n }\n }\n\n const totalPrunedFieldsCount = [\n ...stats.prunedFieldsCountPerDictionary.values(),\n ].reduce((total, count) => total + count, 0);\n const totalPrunedDictionariesCount =\n stats.prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n};\n\n// ── Reporting helpers ─────────────────────────────────────────────────────────\n\n/**\n * Explains the one step the visual editor stands down: renaming the content\n * fields would break the `keyPath` the editor resolves its edits with. Purge\n * and the rest of the minification still run.\n */\nconst logFieldRenameDisabledByEditor = (logger: PurgeLogger): void =>\n logger([\n 'Dictionary field renaming is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n colorize('— the editor needs complete keys', ANSIColors.GREY),\n ]);\n\n/**\n * Reports the dictionaries whose consumption could not be tracked statically,\n * with the source files responsible — those keep their full content.\n */\nconst logUntrackedBindings = (\n logger: PurgeLogger,\n pruneContext: PruneContext\n): void => {\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n};\n\n// ── Main initialisation ───────────────────────────────────────────────────────\n\n/**\n * Runs the full purge/minify pipeline for the given options, using a\n * module-level cache so the work happens at most once per process per\n * unique `baseDir`.\n *\n * Exported so bundler integrations that do not go through Babel can drive the\n * same pipeline. The Next.js integration is the main consumer: its SWC plugin\n * runs as a per-file Wasm transform with no filesystem access, so the analysis\n * and the dictionary rewrite happen here, in Node, and only the resulting\n * field-rename tables (see {@link serializeFieldRenameMap}) are forwarded to\n * the plugin.\n *\n * **This performs file I/O**: it reads every source file listed in\n * `componentFilesList` and overwrites the compiled dictionary JSON files\n * in-place. Call it at most once per build, after the dictionaries have been\n * built.\n */\nexport const runIntlayerPurgePipeline = (\n options: PurgePluginOptions\n): PruneContext => {\n const {\n baseDir,\n purge,\n minify,\n optimize,\n editorEnabled,\n dictionariesDir,\n dynamicDictionariesDir,\n componentFilesList,\n dictionaryKeyToImportModeMap,\n nestedDictionaryReferences,\n compatCallers,\n logConfig,\n } = options;\n\n const cachedContext = _pruneContextCache.get(baseDir);\n if (cachedContext) return cachedContext;\n\n const pruneContext = createPruneContext();\n _pruneContextCache.set(baseDir, pruneContext);\n\n const logger = getAppLogger(logConfig);\n\n const shouldPurge = Boolean(purge);\n const shouldMinify = Boolean(minify);\n\n // Field renaming rewrites the content keys the interpreter walks, so the\n // `keyPath` reported to the visual editor would no longer resolve against\n // the unmerged dictionaries it edits. Purging is keyPath-neutral: a purged\n // field is one no source file reads, so it is never rendered either.\n const isFieldRenameEnabled = shouldMinify && !editorEnabled;\n\n if (editorEnabled && minify) {\n logFieldRenameDisabledByEditor(logger);\n }\n\n if ((!shouldPurge && !shouldMinify) || optimize === false)\n return pruneContext;\n\n // Phase 1: Synchronously analyse all component source files.\n for (const sourceFilePath of componentFilesList) {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) continue;\n analyzeSourceFileSync(sourceFilePath, pruneContext, compatCallers);\n }\n\n // Phase 1.5: Keep `nest()` targets resolvable — must run before the rename\n // maps are built so nest targets are excluded from field renaming.\n if (nestedDictionaryReferences) {\n preserveNestedDictionaryFields(pruneContext, nestedDictionaryReferences);\n }\n\n // Phase 1.6: Warn about the dictionaries the analysis could not follow.\n logUntrackedBindings(logger, pruneContext);\n\n // Phase 2: Build field-rename maps (minify only, editor disabled only).\n if (isFieldRenameEnabled) {\n buildRenameMapsSynchronously(\n dictionariesDir,\n dynamicDictionariesDir,\n dictionaryKeyToImportModeMap,\n pruneContext,\n logger\n );\n }\n\n // Phase 3: Write pruned / minified dictionary JSON files to disk.\n processAllDictionaryFiles(\n dictionariesDir,\n dynamicDictionariesDir,\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger\n );\n\n return pruneContext;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that analyses all project source files and rewrites compiled\n * dictionary JSON files in-place to remove unused content fields\n * (`build.purge`) and/or rename them to short alphabetic aliases\n * (`build.minify`).\n *\n * All option values must be pre-resolved via {@link getPurgePluginOptions}\n * before being passed here — the plugin does not load the intlayer\n * configuration itself.\n *\n * This plugin performs **file I/O as a side effect**: on the very first Babel\n * transform in a given Node.js process it synchronously scans the component\n * files listed in `options.componentFilesList`, builds field-usage data, and\n * writes the processed dictionaries to disk. Subsequent transforms are\n * no-ops.\n *\n * Source-code field renames (rewriting `content.title` → `content.a`) are\n * handled by the companion {@link intlayerMinifyBabelPlugin}.\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 * - Intended for **production builds** only. Dictionary JSON files are\n * overwritten in-place; running `intlayer build` afterwards restores the\n * originals.\n * - The plugin is a no-op when `optimize` is `false`.\n * - When `editorEnabled` is `true`, purge and minify still run but field\n * renaming is skipped, so the `keyPath` the visual editor receives keeps\n * matching the unmerged dictionaries it edits.\n */\nexport const intlayerPurgeBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject => ({\n name: 'intlayer-purge',\n\n pre(this: PluginPass) {\n runIntlayerPurgePipeline(this.opts as PurgePluginOptions);\n },\n\n visitor: {\n // No AST transforms: all work is done as a side effect in pre().\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"babel-plugin-intlayer-purge.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-purge.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { PluginObject, PluginPass } from '@babel/core';\nimport { transformSync } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\nimport { formatPath } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n buildNestedRenameMapFromContent,\n getNestedRenameEntryAtPath,\n} from './babel-plugin-intlayer-field-rename';\nimport {\n type CompatCallerConfig,\n createPruneContext,\n makeUsageAnalyzerBabelPlugin,\n type NestedRenameMap,\n type PruneContext,\n preserveNestedDictionaryFields,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks } from './extractScriptBlocks';\nimport {\n BABEL_PARSER_OPTIONS,\n getUsageCheckRegex,\n SOURCE_FILE_REGEX,\n} from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerPurgeBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getPurgePluginOptions}) so the plugin itself does not need to read\n * the configuration file on every file transform.\n */\nexport type PurgePluginOptions = {\n /**\n * Absolute path to the project root. Used as the cache key for the shared\n * {@link PruneContext} so two Babel transform pipelines for different\n * workspaces in the same process do not share state.\n */\n baseDir: string;\n\n /**\n * When `true`, remove unused content fields from compiled dictionary JSON\n * files. Mirrors `build.purge` in `intlayer.config.ts`.\n */\n purge: boolean;\n\n /**\n * When `true`, rename content fields to short alphabetic aliases\n * (`title` → `a`, etc.) and strip top-level metadata from compiled\n * dictionaries. Mirrors `build.minify` in `intlayer.config.ts`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. `undefined` means \"auto\" (active for\n * production builds). When explicitly `false`, the plugin is a no-op.\n * Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin still purges and minifies, but skips field renaming\n * so the `keyPath` reported to the visual editor keeps matching the unmerged\n * dictionaries it edits. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n\n /**\n * Absolute path to the compiled static dictionaries directory\n * (`.intlayer/dictionaries/` by default).\n */\n dictionariesDir: string;\n\n /**\n * Absolute path to the compiled per-locale dynamic dictionaries directory\n * (`.intlayer/dynamic_dictionaries/` by default).\n */\n dynamicDictionariesDir: string;\n\n /**\n * Pre-built list of component source file paths to analyse for field-usage.\n * Populated by {@link getPurgePluginOptions} from the intlayer config's\n * `content` glob patterns.\n */\n componentFilesList: string[];\n\n /**\n * Dictionaries referenced through `nest()`, mapped to the top-level fields\n * those references read (`'all'` when the whole dictionary is nested).\n *\n * Those fields are resolved at runtime by `getNesting` using their original\n * names, so they must survive purging and must never be renamed.\n */\n nestedDictionaryReferences?: Map<string, NestedFieldReferences>;\n\n /**\n * Per-dictionary import-mode overrides, keyed by dictionary `key`.\n * Dictionaries with mode `'fetch'` are excluded from field renaming because\n * their JSON is served from a remote API using the original field names.\n */\n dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n\n /**\n * Compat-adapter namespace caller configurations.\n *\n * When set, the usage analyser recognises these additional translation\n * function patterns and maps them to dictionary field usage. Each compat\n * adapter package (e.g. `@intlayer/react-i18next`) provides its own caller\n * list; they are NOT hardcoded here so that `@intlayer/babel` stays\n * framework-agnostic.\n *\n * Defaults to `[]` (no compat callers) when omitted.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Logging configuration used to report what the pipeline purged and\n * minified, so a Babel- or SWC-driven build prints the same summary as the\n * Vite one. Mirrors `log` in `intlayer.config.ts`; omit to fall back to the\n * logger defaults.\n */\n logConfig?: Pick<IntlayerConfig, 'log'>;\n};\n\n/** Application logger used to report the pipeline's progress. */\ntype PurgeLogger = ReturnType<typeof getAppLogger>;\n\n// ── Shared module-level state ─────────────────────────────────────────────────\n\n/**\n * Cache of built {@link PruneContext} objects, keyed by the project's\n * `baseDir`. Each context is built exactly once per Node.js process:\n * {@link runIntlayerPurgePipeline} registers the context before running so every\n * subsequent transform is a no-op cache hit.\n */\nconst _pruneContextCache = new Map<string, PruneContext>();\n\n/**\n * Returns the shared {@link PruneContext} for the given base directory, or\n * `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for\n * that directory.\n *\n * Used by {@link intlayerMinifyBabelPlugin} to read the rename map without\n * creating a circular dependency.\n */\nexport const getSharedPruneContext = (baseDir: string): PruneContext | null =>\n _pruneContextCache.get(baseDir) ?? null;\n\n// ── Dictionary JSON types ─────────────────────────────────────────────────────\n\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string;\n [extraKey: string]: unknown;\n};\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// ── Prune helpers (mirrors intlayerPrunePlugin) ───────────────────────────────\n\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/** Returns a copy of `record` keeping only the keys listed in `usedFieldNames`. */\nconst filterRecordByUsedFields = (\n record: Record<string, unknown>,\n usedFieldNames: Set<string>\n): Record<string, unknown> => {\n const filteredRecord: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(record)) {\n if (usedFieldNames.has(fieldName)) {\n filteredRecord[fieldName] = fieldValue;\n }\n }\n return filteredRecord;\n};\n\n/**\n * Removes unused fields from a **static** dictionary (all locales in one\n * file). Supports shape A (translation node at the root) and shape B (flat\n * record of translation nodes per field).\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A: { nodeType: \"translation\", translation: { en: { f1, f2 } } }\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n if (isPlainRecord(firstLocaleValue)) {\n const prunedTranslation: Record<string, unknown> = {};\n for (const [locale, localeContent] of Object.entries(\n content.translation\n )) {\n prunedTranslation[locale] = isPlainRecord(localeContent)\n ? filterRecordByUsedFields(localeContent, usedFieldNames)\n : localeContent;\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslation },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B: { field1: { nodeType: \"translation\", … }, field2: { … } }\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Removes unused fields from a **dynamic / per-locale** dictionary file\n * (one JSON per locale, flat `content` record).\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n};\n\n// ── Minify helpers (mirrors intlayerMinifyPlugin) ─────────────────────────────\n\n/**\n * Recursively renames user-defined content fields using `renameMap`.\n * Translation nodes, arrays, and primitives follow the same traversal rules\n * as in the Vite-based minify plugin.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale value with the same map.\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values.\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val;\n }\n }\n return result;\n};\n\n/**\n * Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming\n * only the keys inside `content` while leaving top-level metadata untouched.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// ── Synchronous source-file analysis ─────────────────────────────────────────\n\n/**\n * Runs the usage-analyser Babel plugin synchronously on a single code block,\n * accumulating results into `pruneContext`.\n */\nconst analyzeCodeBlockSync = (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n try {\n transformSync(code, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false,\n });\n } catch {\n pruneContext.hasUnparsableSourceFiles = true;\n }\n};\n\n/**\n * Reads a source file from disk and runs the usage-analyser synchronously.\n * SFC files (Vue / Svelte) are handled by extracting script blocks first.\n */\nconst analyzeSourceFileSync = (\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n let code: string;\n try {\n code = readFileSync(sourceFilePath, 'utf-8');\n } catch {\n return;\n }\n\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n\n if (!usageCheckRegex.test(code)) return;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n analyzeCodeBlockSync(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n// ── Build rename maps ─────────────────────────────────────────────────────────\n\n/**\n * Reads compiled dictionary JSON files to build the nested field-rename maps,\n * mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's\n * `buildStart` hook. Results are stored in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n */\nconst buildRenameMapsSynchronously = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n dictionaryKeyToImportModeMap: PurgePluginOptions['dictionaryKeyToImportModeMap'],\n pruneContext: PruneContext,\n logger: PurgeLogger\n): void => {\n let partiallyMinifiedDictionariesCount = 0;\n\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch') continue;\n if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey))\n continue;\n\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);\n if (existsSync(staticJsonPath)) {\n try {\n const raw = readFileSync(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n // Fall through to dynamic dict.\n }\n }\n\n if (!dictionaryContent) {\n const dynamicDirPath = join(dynamicDictionariesDir, dictionaryKey);\n if (existsSync(dynamicDirPath)) {\n try {\n const localeFiles = readdirSync(dynamicDirPath);\n const firstJsonFile = localeFiles.find((file) =>\n file.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = readFileSync(\n join(dynamicDirPath, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary not readable – skip rename for this key.\n }\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent);\n\n // Preserve children of opaque values to avoid breaking child components.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n if (opaqueFieldMap) {\n const dangerousOccurrences = [...opaqueFieldMap.values()].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(nestedRenameMap, occurrence.fieldPath)\n ?.children.size ?? 0) > 0\n );\n\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(`'${occurrence.fieldPath.join('.')}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (location) => `\\n at ${formatPath(location)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n for (const occurrence of dangerousOccurrences) {\n const renameEntry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (renameEntry) {\n renameEntry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n }\n};\n\n// ── Dictionary file writing ───────────────────────────────────────────────────\n\n/**\n * Names of the top-level content fields of a compiled dictionary, whatever\n * shape its `content` uses. Used to report which fields the purge removed.\n */\nconst readContentFieldNames = (\n content: CompiledDictionaryJson['content']\n): string[] => {\n // Shape A – fields live inside each locale object.\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n return isPlainRecord(firstLocaleValue) ? Object.keys(firstLocaleValue) : [];\n }\n\n // Shape B / dynamic – flat content record.\n if (isPlainRecord(content)) return Object.keys(content);\n\n return [];\n};\n\n/**\n * Running totals of the purge, reported once the whole pipeline is done.\n */\ntype PurgeStats = {\n /** Dictionary key → number of fields removed from it. */\n prunedFieldsCountPerDictionary: Map<string, number>;\n /**\n * Dictionary keys whose \"pruned fields\" line has already been printed. A\n * key spans several files (one static, one per locale), and they all remove\n * the same fields.\n */\n loggedPrunedDictionaryKeys: Set<string>;\n /**\n * Dictionary keys whose \"analysis is incomplete\" warning has already been\n * printed, deduplicated the same way.\n */\n loggedIncompleteAnalysisKeys: Set<string>;\n};\n\n/**\n * Reads a compiled dictionary JSON file, applies the purge and/or minify\n * transformations, and writes it back in-place when something changed.\n *\n * `dictionaryKind` selects the prune strategy and the minified output shape:\n * static dictionaries keep `{ key, content }`, dynamic per-locale\n * dictionaries additionally keep `locale`.\n */\nconst processDictionaryFile = (\n filePath: string,\n dictionaryKind: 'static' | 'dynamic',\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger,\n stats: PurgeStats\n): void => {\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(\n readFileSync(filePath, 'utf-8')\n ) as CompiledDictionaryJson;\n } catch {\n return; // Unreadable or invalid JSON – leave file unchanged.\n }\n\n const { key: dictionaryKey } = parsedDict;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(filePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const pruneDictionaryContent =\n dictionaryKind === 'static'\n ? pruneStaticDictionaryContent\n : pruneDynamicDictionaryContent;\n const originalFieldNames = readContentFieldNames(parsedDict.content);\n const { prunedDictionary, wasRecognised } = pruneDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(filePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n stats.prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!stats.loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n stats.loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n parsedDict = prunedDictionary;\n modified = true;\n } else if (\n !fieldUsage &&\n pruneContext.hasUnparsableSourceFiles &&\n !stats.loggedIncompleteAnalysisKeys.has(dictionaryKey)\n ) {\n // An unparsable source file might be the one referencing this key, so its\n // usage set cannot be trusted to be empty.\n stats.loggedIncompleteAnalysisKeys.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? dictionaryKind === 'static'\n ? { key: parsedDict.key, content: parsedDict.content }\n : {\n key: parsedDict.key,\n content: parsedDict.content,\n locale: parsedDict.locale,\n }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processAllDictionaryFiles = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger\n): void => {\n const stats: PurgeStats = {\n prunedFieldsCountPerDictionary: new Map(),\n loggedPrunedDictionaryKeys: new Set(),\n loggedIncompleteAnalysisKeys: new Set(),\n };\n\n if (existsSync(dictionariesDir)) {\n for (const entry of readdirSync(dictionariesDir)) {\n if (!entry.endsWith('.json')) continue;\n processDictionaryFile(\n join(dictionariesDir, entry),\n 'static',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n }\n\n if (existsSync(dynamicDictionariesDir)) {\n for (const keyDir of readdirSync(dynamicDictionariesDir)) {\n const keyDirPath = join(dynamicDictionariesDir, keyDir);\n try {\n for (const localeFile of readdirSync(keyDirPath)) {\n if (!localeFile.endsWith('.json')) continue;\n processDictionaryFile(\n join(keyDirPath, localeFile),\n 'dynamic',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n } catch {\n // Unreadable key directory – skip.\n }\n }\n }\n\n const totalPrunedFieldsCount = [\n ...stats.prunedFieldsCountPerDictionary.values(),\n ].reduce((total, count) => total + count, 0);\n const totalPrunedDictionariesCount =\n stats.prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n};\n\n// ── Reporting helpers ─────────────────────────────────────────────────────────\n\n/**\n * Explains the one step the visual editor stands down: renaming the content\n * fields would break the `keyPath` the editor resolves its edits with. Purge\n * and the rest of the minification still run.\n */\nconst logFieldRenameDisabledByEditor = (logger: PurgeLogger): void =>\n logger([\n 'Dictionary field renaming is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n colorize('— the editor needs complete keys', ANSIColors.GREY),\n ]);\n\n/**\n * Reports the dictionaries whose consumption could not be tracked statically,\n * with the source files responsible — those keep their full content.\n */\nconst logUntrackedBindings = (\n logger: PurgeLogger,\n pruneContext: PruneContext\n): void => {\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n};\n\n// ── Main initialisation ───────────────────────────────────────────────────────\n\n/**\n * Runs the full purge/minify pipeline for the given options, using a\n * module-level cache so the work happens at most once per process per\n * unique `baseDir`.\n *\n * Exported so bundler integrations that do not go through Babel can drive the\n * same pipeline. The Next.js integration is the main consumer: its SWC plugin\n * runs as a per-file Wasm transform with no filesystem access, so the analysis\n * and the dictionary rewrite happen here, in Node, and only the resulting\n * field-rename tables (see {@link serializeFieldRenameMap}) are forwarded to\n * the plugin.\n *\n * **This performs file I/O**: it reads every source file listed in\n * `componentFilesList` and overwrites the compiled dictionary JSON files\n * in-place. Call it at most once per build, after the dictionaries have been\n * built.\n */\nexport const runIntlayerPurgePipeline = (\n options: PurgePluginOptions\n): PruneContext => {\n const {\n baseDir,\n purge,\n minify,\n optimize,\n editorEnabled,\n dictionariesDir,\n dynamicDictionariesDir,\n componentFilesList,\n dictionaryKeyToImportModeMap,\n nestedDictionaryReferences,\n compatCallers,\n logConfig,\n } = options;\n\n const cachedContext = _pruneContextCache.get(baseDir);\n if (cachedContext) return cachedContext;\n\n const pruneContext = createPruneContext();\n _pruneContextCache.set(baseDir, pruneContext);\n\n const logger = getAppLogger(logConfig);\n\n const shouldPurge = Boolean(purge);\n const shouldMinify = Boolean(minify);\n\n // Field renaming rewrites the content keys the interpreter walks, so the\n // `keyPath` reported to the visual editor would no longer resolve against\n // the unmerged dictionaries it edits. Purging is keyPath-neutral: a purged\n // field is one no source file reads, so it is never rendered either.\n const isFieldRenameEnabled = shouldMinify && !editorEnabled;\n\n if (editorEnabled && minify) {\n logFieldRenameDisabledByEditor(logger);\n }\n\n if ((!shouldPurge && !shouldMinify) || optimize === false)\n return pruneContext;\n\n // Phase 1: Synchronously analyse all component source files.\n for (const sourceFilePath of componentFilesList) {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) continue;\n analyzeSourceFileSync(sourceFilePath, pruneContext, compatCallers);\n }\n\n // Phase 1.5: Keep `nest()` targets resolvable — must run before the rename\n // maps are built so nest targets are excluded from field renaming.\n if (nestedDictionaryReferences) {\n preserveNestedDictionaryFields(pruneContext, nestedDictionaryReferences);\n }\n\n // Phase 1.6: Warn about the dictionaries the analysis could not follow.\n logUntrackedBindings(logger, pruneContext);\n\n // Phase 2: Build field-rename maps (minify only, editor disabled only).\n if (isFieldRenameEnabled) {\n buildRenameMapsSynchronously(\n dictionariesDir,\n dynamicDictionariesDir,\n dictionaryKeyToImportModeMap,\n pruneContext,\n logger\n );\n }\n\n // Phase 3: Write pruned / minified dictionary JSON files to disk.\n processAllDictionaryFiles(\n dictionariesDir,\n dynamicDictionariesDir,\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger\n );\n\n return pruneContext;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that analyses all project source files and rewrites compiled\n * dictionary JSON files in-place to remove unused content fields\n * (`build.purge`) and/or rename them to short alphabetic aliases\n * (`build.minify`).\n *\n * All option values must be pre-resolved via {@link getPurgePluginOptions}\n * before being passed here — the plugin does not load the intlayer\n * configuration itself.\n *\n * This plugin performs **file I/O as a side effect**: on the very first Babel\n * transform in a given Node.js process it synchronously scans the component\n * files listed in `options.componentFilesList`, builds field-usage data, and\n * writes the processed dictionaries to disk. Subsequent transforms are\n * no-ops.\n *\n * Source-code field renames (rewriting `content.title` → `content.a`) are\n * handled by the companion {@link intlayerMinifyBabelPlugin}.\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 * - Intended for **production builds** only. Dictionary JSON files are\n * overwritten in-place; running `intlayer build` afterwards restores the\n * originals.\n * - The plugin is a no-op when `optimize` is `false`.\n * - When `editorEnabled` is `true`, purge and minify still run but field\n * renaming is skipped, so the `keyPath` the visual editor receives keeps\n * matching the unmerged dictionaries it edits.\n */\nexport const intlayerPurgeBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject => ({\n name: 'intlayer-purge',\n\n pre(this: PluginPass) {\n runIntlayerPurgePipeline(this.opts as PurgePluginOptions);\n },\n\n visitor: {\n // No AST transforms: all work is done as a side effect in pre().\n },\n});\n"],"mappings":"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"}