@intlayer/babel 8.12.2 → 8.12.4-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/cjs/babel-plugin-intlayer-extract.cjs +2 -1
  2. package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
  3. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +14 -9
  4. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
  5. package/dist/cjs/babel-plugin-intlayer-minify.cjs +83 -0
  6. package/dist/cjs/babel-plugin-intlayer-minify.cjs.map +1 -0
  7. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +58 -24
  8. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  9. package/dist/cjs/babel-plugin-intlayer-purge.cjs +403 -0
  10. package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -0
  11. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
  12. package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
  13. package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
  14. package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
  15. package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
  16. package/dist/cjs/extractContent/utils/constants.cjs.map +1 -1
  17. package/dist/cjs/extractContent/utils/detectPackageName.cjs +1 -0
  18. package/dist/cjs/extractContent/utils/detectPackageName.cjs.map +1 -1
  19. package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs.map +1 -1
  20. package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs +1 -0
  21. package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs.map +1 -1
  22. package/dist/cjs/extractContent/utils/generateKey.cjs.map +1 -1
  23. package/dist/cjs/extractContent/utils/getComponentName.cjs.map +1 -1
  24. package/dist/cjs/extractContent/utils/getExistingIntlayerInfo.cjs.map +1 -1
  25. package/dist/cjs/extractContent/utils/getOrGenerateKey.cjs.map +1 -1
  26. package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs +1 -0
  27. package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs.map +1 -1
  28. package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
  29. package/dist/cjs/extractScriptBlocks.cjs +1 -0
  30. package/dist/cjs/extractScriptBlocks.cjs.map +1 -1
  31. package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
  32. package/dist/cjs/getOptimizePluginOptions.cjs +1 -0
  33. package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
  34. package/dist/cjs/getPurgePluginOptions.cjs +108 -0
  35. package/dist/cjs/getPurgePluginOptions.cjs.map +1 -0
  36. package/dist/cjs/index.cjs +9 -1
  37. package/dist/cjs/transformers.cjs +1 -0
  38. package/dist/cjs/transformers.cjs.map +1 -1
  39. package/dist/esm/babel-plugin-intlayer-extract.mjs +2 -1
  40. package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
  41. package/dist/esm/babel-plugin-intlayer-field-rename.mjs +14 -9
  42. package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
  43. package/dist/esm/babel-plugin-intlayer-minify.mjs +82 -0
  44. package/dist/esm/babel-plugin-intlayer-minify.mjs.map +1 -0
  45. package/dist/esm/babel-plugin-intlayer-optimize.mjs +57 -24
  46. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  47. package/dist/esm/babel-plugin-intlayer-purge.mjs +400 -0
  48. package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -0
  49. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
  50. package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
  51. package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
  52. package/dist/esm/extractContent/extractContent.mjs.map +1 -1
  53. package/dist/esm/extractContent/processTsxFile.mjs.map +1 -1
  54. package/dist/esm/extractContent/utils/constants.mjs.map +1 -1
  55. package/dist/esm/extractContent/utils/detectPackageName.mjs.map +1 -1
  56. package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs.map +1 -1
  57. package/dist/esm/extractContent/utils/extractDictionaryKey.mjs.map +1 -1
  58. package/dist/esm/extractContent/utils/generateKey.mjs.map +1 -1
  59. package/dist/esm/extractContent/utils/getComponentName.mjs.map +1 -1
  60. package/dist/esm/extractContent/utils/getExistingIntlayerInfo.mjs.map +1 -1
  61. package/dist/esm/extractContent/utils/getOrGenerateKey.mjs.map +1 -1
  62. package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs.map +1 -1
  63. package/dist/esm/extractContent/utils/shouldExtract.mjs.map +1 -1
  64. package/dist/esm/extractScriptBlocks.mjs.map +1 -1
  65. package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
  66. package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
  67. package/dist/esm/getPurgePluginOptions.mjs +106 -0
  68. package/dist/esm/getPurgePluginOptions.mjs.map +1 -0
  69. package/dist/esm/index.mjs +5 -2
  70. package/dist/esm/transformers.mjs.map +1 -1
  71. package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
  72. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  73. package/dist/types/babel-plugin-intlayer-minify.d.ts +84 -0
  74. package/dist/types/babel-plugin-intlayer-minify.d.ts.map +1 -0
  75. package/dist/types/babel-plugin-intlayer-optimize.d.ts +10 -5
  76. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  77. package/dist/types/babel-plugin-intlayer-purge.d.ts +127 -0
  78. package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -0
  79. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
  80. package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
  81. package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
  82. package/dist/types/extractContent/extractContent.d.ts.map +1 -1
  83. package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
  84. package/dist/types/extractContent/utils/detectPackageName.d.ts.map +1 -1
  85. package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts.map +1 -1
  86. package/dist/types/extractContent/utils/extractDictionaryKey.d.ts.map +1 -1
  87. package/dist/types/extractContent/utils/generateKey.d.ts.map +1 -1
  88. package/dist/types/extractContent/utils/getComponentName.d.ts.map +1 -1
  89. package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts.map +1 -1
  90. package/dist/types/extractContent/utils/shouldExtract.d.ts.map +1 -1
  91. package/dist/types/extractScriptBlocks.d.ts.map +1 -1
  92. package/dist/types/getExtractPluginOptions.d.ts.map +1 -1
  93. package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
  94. package/dist/types/getPurgePluginOptions.d.ts +83 -0
  95. package/dist/types/getPurgePluginOptions.d.ts.map +1 -0
  96. package/dist/types/index.d.ts +4 -1
  97. package/dist/types/transformers.d.ts.map +1 -1
  98. package/package.json +11 -11
@@ -12,9 +12,12 @@ const require_extractContent_utils_getComponentName = require('./extractContent/
12
12
  const require_extractContent_extractContent = require('./extractContent/extractContent.cjs');
13
13
  const require_babel_plugin_intlayer_extract = require('./babel-plugin-intlayer-extract.cjs');
14
14
  const require_babel_plugin_intlayer_field_rename = require('./babel-plugin-intlayer-field-rename.cjs');
15
- const require_getOptimizePluginOptions = require('./getOptimizePluginOptions.cjs');
16
15
  const require_babel_plugin_intlayer_optimize = require('./babel-plugin-intlayer-optimize.cjs');
17
16
  const require_transformers = require('./transformers.cjs');
17
+ const require_babel_plugin_intlayer_purge = require('./babel-plugin-intlayer-purge.cjs');
18
+ const require_babel_plugin_intlayer_minify = require('./babel-plugin-intlayer-minify.cjs');
19
+ const require_getOptimizePluginOptions = require('./getOptimizePluginOptions.cjs');
20
+ const require_getPurgePluginOptions = require('./getPurgePluginOptions.cjs');
18
21
 
19
22
  exports.ATTRIBUTES_TO_EXTRACT = require_extractContent_utils_constants.ATTRIBUTES_TO_EXTRACT;
20
23
  exports.BABEL_PARSER_OPTIONS = require_transformers.BABEL_PARSER_OPTIONS;
@@ -39,11 +42,16 @@ exports.generateKey = require_extractContent_utils_generateKey.generateKey;
39
42
  exports.generateShortFieldName = require_babel_plugin_intlayer_field_rename.generateShortFieldName;
40
43
  exports.getComponentName = require_extractContent_utils_getComponentName.getComponentName;
41
44
  exports.getExtractPluginOptions = require_getExtractPluginOptions.getExtractPluginOptions;
45
+ exports.getMinifyPluginOptions = require_getPurgePluginOptions.getMinifyPluginOptions;
42
46
  exports.getOptimizePluginOptions = require_getOptimizePluginOptions.getOptimizePluginOptions;
43
47
  exports.getOutput = require_extractContent_utils_extractDictionaryInfo.getOutput;
48
+ exports.getPurgePluginOptions = require_getPurgePluginOptions.getPurgePluginOptions;
49
+ exports.getSharedPruneContext = require_babel_plugin_intlayer_purge.getSharedPruneContext;
44
50
  exports.injectScriptBlocks = require_extractScriptBlocks.injectScriptBlocks;
45
51
  exports.intlayerExtractBabelPlugin = require_babel_plugin_intlayer_extract.intlayerExtractBabelPlugin;
52
+ exports.intlayerMinifyBabelPlugin = require_babel_plugin_intlayer_minify.intlayerMinifyBabelPlugin;
46
53
  exports.intlayerOptimizeBabelPlugin = require_babel_plugin_intlayer_optimize.intlayerOptimizeBabelPlugin;
54
+ exports.intlayerPurgeBabelPlugin = require_babel_plugin_intlayer_purge.intlayerPurgeBabelPlugin;
47
55
  exports.makeFieldRenameBabelPlugin = require_babel_plugin_intlayer_field_rename.makeFieldRenameBabelPlugin;
48
56
  exports.makeUsageAnalyzerBabelPlugin = require_babel_plugin_intlayer_usage_analyzer.makeUsageAnalyzerBabelPlugin;
49
57
  exports.mergeWithExistingMultilingualDictionary = require_extractContent_contentWriter.mergeWithExistingMultilingualDictionary;
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
3
  const require_babel_plugin_intlayer_usage_analyzer = require('./babel-plugin-intlayer-usage-analyzer.cjs');
3
4
  const require_extractScriptBlocks = require('./extractScriptBlocks.cjs');
4
5
  const require_babel_plugin_intlayer_field_rename = require('./babel-plugin-intlayer-field-rename.cjs');
@@ -1 +1 @@
1
- {"version":3,"file":"transformers.cjs","names":["makeUsageAnalyzerBabelPlugin","extractScriptBlocks","makeFieldRenameBabelPlugin","injectScriptBlocks","intlayerOptimizeBabelPlugin"],"sources":["../../src/transformers.ts"],"sourcesContent":["import { type TransformOptions, transformAsync } from '@babel/core';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: NonNullable<TransformOptions['parserOpts']> =\n {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n };\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer`). Used by the optimize/transform pass, which\n * only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = /\\b(use|get)Intlayer\\b/;\n\n/**\n * Fast pre-check: matches files that could contain compat-adapter namespace\n * callers (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`). These are analysed for field usage (pruning) but never rewritten.\n */\nexport const COMPAT_USAGE_REGEX =\n /\\b(useTranslation|useTranslations|getTranslations|getFixedT|useI18n)\\b/;\n\n/**\n * Fast pre-check for the usage-analysis phase: matches files containing either\n * native intlayer calls or compat-adapter namespace callers.\n */\nexport const INTLAYER_OR_COMPAT_USAGE_REGEX =\n /\\b(useIntlayer|getIntlayer|useTranslation|useTranslations|getTranslations|getFixedT|useI18n)\\b/;\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!INTLAYER_OR_COMPAT_USAGE_REGEX.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAa,uBACX;CACE,YAAY;CACZ,6BAA6B;CAC7B,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;AAOF,MAAa,uBAAuB;;;;;;AAOpC,MAAa,qBACX;;;;;AAMF,MAAa,iCACX;;;;;;AAOF,MAAa,oBAAoB;;;;;;;AAUjC,MAAM,uBAAuB,OAC3B,eACA,gBACA,cACA,kBACkB;CAClB,sCAAqB,eAAe;EAClC,UAAU;EACV,SAAS,CAACA,0EAA6B,cAAc,EAAE,cAAc,CAAC,CAAC;EACvE,YAAY;EACZ,KAAK;EACL,MAAM;CACR,CAAC;AACH;;;;;;;;;;;;;AAcA,MAAa,0BAA0B,OACrC,gBACA,MACA,cACA,kBACkB;CAClB,MAAM,eAAeC,gDAAoB,gBAAgB,IAAI;CAM7D,KAAK,MAAM,SAAS,cAAc;EAChC,IAAI,CAAC,+BAA+B,KAAK,MAAM,OAAO,GAAG;EACzD,MAAM,qBACJ,MAAM,SACN,gBACA,cACA,aACF;CACF;AACF;;;;;;;AAQA,MAAa,qBAAqB,OAChC,MACA,gBACA,iBAC2B;CAC3B,IAAI;EAOF,QAAO,sCAN6B,MAAM;GACxC,UAAU;GACV,SAAS,CAACC,sEAA2B,YAAY,CAAC;GAClD,YAAY;GACZ,KAAK;EACP,CAAC,IACc,QAAQ;CACzB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;AASA,MAAa,2BAA2B,OACtC,gBACA,MACA,iBAC2B;CAC3B,IAAI,aAAa,8BAA8B,SAAS,GAAG,OAAO;CAClE,IAAI,CAAC,qBAAqB,KAAK,IAAI,GAAG,OAAO;CAE7C,MAAM,eAAeD,gDAAoB,gBAAgB,IAAI;CAM7D,IAHE,aAAa,SAAS,OACpB,aAAa,IAAI,sBAAsB,KAAK,KAAK,aAAa,SAAS,IAEhE;EAET,MAAM,gBAGD,CAAC;EAEN,KAAK,MAAM,SAAS,cAAc;GAChC,IAAI,CAAC,qBAAqB,KAAK,MAAM,OAAO,GAAG;GAE/C,MAAM,cAAc,MAAM,mBACxB,MAAM,SACN,gBACA,YACF;GACA,IAAI,eAAe,gBAAgB,MAAM,SACvC,cAAc,KAAK;IAAE;IAAO,iBAAiB;GAAY,CAAC;EAE9D;EAEA,IAAI,cAAc,WAAW,GAAG,OAAO;EACvC,OAAOE,+CAAmB,MAAM,aAAa;CAC/C;CAGA,OAAO,mBAAmB,MAAM,gBAAgB,YAAY;AAC9D;;;;;;;;;AAUA,MAAa,qBAAqB,OAChC,MACA,gBACA,YAIW;CACX,MAAM,SAAS,sCAAqB,MAAM;EACxC,UAAU;EACV,SAAS,CAAC,CAACC,oEAA6B,OAAO,CAAC;EAChD,YAAY;CACd,CAAC;CAED,IAAI,CAAC,QAAQ,MAAM,OAAO;CAE1B,OAAO;EAAE,MAAM,OAAO;EAAM,KAAK,OAAO;CAAI;AAC9C"}
1
+ {"version":3,"file":"transformers.cjs","names":["makeUsageAnalyzerBabelPlugin","extractScriptBlocks","makeFieldRenameBabelPlugin","injectScriptBlocks","intlayerOptimizeBabelPlugin"],"sources":["../../src/transformers.ts"],"sourcesContent":["import { type TransformOptions, transformAsync } from '@babel/core';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: NonNullable<TransformOptions['parserOpts']> =\n {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n };\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer`). Used by the optimize/transform pass, which\n * only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = /\\b(use|get)Intlayer\\b/;\n\n/**\n * Fast pre-check: matches files that could contain compat-adapter namespace\n * callers (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`). These are analysed for field usage (pruning) but never rewritten.\n */\nexport const COMPAT_USAGE_REGEX =\n /\\b(useTranslation|useTranslations|getTranslations|getFixedT|useI18n)\\b/;\n\n/**\n * Fast pre-check for the usage-analysis phase: matches files containing either\n * native intlayer calls or compat-adapter namespace callers.\n */\nexport const INTLAYER_OR_COMPAT_USAGE_REGEX =\n /\\b(useIntlayer|getIntlayer|useTranslation|useTranslations|getTranslations|getFixedT|useI18n)\\b/;\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!INTLAYER_OR_COMPAT_USAGE_REGEX.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":";;;;;;;;;;;;;AAmBA,MAAa,uBACX;CACE,YAAY;CACZ,6BAA6B;CAC7B,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACF;;;;;;AAOH,MAAa,uBAAuB;;;;;;AAOpC,MAAa,qBACX;;;;;AAMF,MAAa,iCACX;;;;;;AAOF,MAAa,oBAAoB;;;;;;;AAUjC,MAAM,uBAAuB,OAC3B,eACA,gBACA,cACA,kBACkB;AAClB,uCAAqB,eAAe;EAClC,UAAU;EACV,SAAS,CAACA,0EAA6B,cAAc,EAAE,eAAe,CAAC,CAAC;EACxE,YAAY;EACZ,KAAK;EACL,MAAM;EACP,CAAC;;;;;;;;;;;;;;AAeJ,MAAa,0BAA0B,OACrC,gBACA,MACA,cACA,kBACkB;CAClB,MAAM,eAAeC,gDAAoB,gBAAgB,KAAK;AAM9D,MAAK,MAAM,SAAS,cAAc;AAChC,MAAI,CAAC,+BAA+B,KAAK,MAAM,QAAQ,CAAE;AACzD,QAAM,qBACJ,MAAM,SACN,gBACA,cACA,cACD;;;;;;;;;AAUL,MAAa,qBAAqB,OAChC,MACA,gBACA,iBAC2B;AAC3B,KAAI;AAOF,UAAO,sCAN6B,MAAM;GACxC,UAAU;GACV,SAAS,CAACC,sEAA2B,aAAa,CAAC;GACnD,YAAY;GACZ,KAAK;GACN,CAAC,GACa,QAAQ;SACjB;AACN,SAAO;;;;;;;;;;AAWX,MAAa,2BAA2B,OACtC,gBACA,MACA,iBAC2B;AAC3B,KAAI,aAAa,8BAA8B,SAAS,EAAG,QAAO;AAClE,KAAI,CAAC,qBAAqB,KAAK,KAAK,CAAE,QAAO;CAE7C,MAAM,eAAeD,gDAAoB,gBAAgB,KAAK;AAM9D,KAHE,aAAa,SAAS,OACpB,aAAa,IAAI,sBAAsB,KAAK,KAAK,aAAa,SAAS,IAEhE;EAET,MAAM,gBAGD,EAAE;AAEP,OAAK,MAAM,SAAS,cAAc;AAChC,OAAI,CAAC,qBAAqB,KAAK,MAAM,QAAQ,CAAE;GAE/C,MAAM,cAAc,MAAM,mBACxB,MAAM,SACN,gBACA,aACD;AACD,OAAI,eAAe,gBAAgB,MAAM,QACvC,eAAc,KAAK;IAAE;IAAO,iBAAiB;IAAa,CAAC;;AAI/D,MAAI,cAAc,WAAW,EAAG,QAAO;AACvC,SAAOE,+CAAmB,MAAM,cAAc;;AAIhD,QAAO,mBAAmB,MAAM,gBAAgB,aAAa;;;;;;;;;;AAW/D,MAAa,qBAAqB,OAChC,MACA,gBACA,YAIW;CACX,MAAM,SAAS,sCAAqB,MAAM;EACxC,UAAU;EACV,SAAS,CAAC,CAACC,oEAA6B,QAAQ,CAAC;EACjD,YAAY;EACb,CAAC;AAEF,KAAI,CAAC,QAAQ,KAAM,QAAO;AAE1B,QAAO;EAAE,MAAM,OAAO;EAAM,KAAK,OAAO;EAAK"}
@@ -3,6 +3,7 @@ import { extractContentSync } from "./extractContent/extractContent.mjs";
3
3
  import { relative } from "node:path";
4
4
  import * as ANSIColors from "@intlayer/config/colors";
5
5
  import { colorize, colorizePath, getAppLogger } from "@intlayer/config/logger";
6
+ import { normalizePath } from "@intlayer/config/utils";
6
7
  import { parse } from "@babel/parser";
7
8
 
8
9
  //#region src/babel-plugin-intlayer-extract.ts
@@ -31,7 +32,7 @@ const intlayerExtractBabelPlugin = (_babel) => {
31
32
  if (opts.enabled === false) return;
32
33
  const filename = state.file.opts.filename;
33
34
  if (!filename) return;
34
- if (opts.filesList && !opts.filesList.includes(filename)) return;
35
+ if (opts.filesList && !opts.filesList.map(normalizePath).includes(normalizePath(filename))) return;
35
36
  const fileCode = state.file.code ?? "";
36
37
  if (!fileCode) return;
37
38
  const appLogger = getAppLogger(opts.configuration);
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-extract.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { PluginObj, PluginPass } from '@babel/core';\nimport { parse } from '@babel/parser';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport { extractContentSync } from './extractContent/extractContent';\nimport type { PackageName } from './extractContent/utils/constants';\nimport { detectPackageName } from './extractContent/utils/detectPackageName';\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: Record<string, string>;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n packageName?: PackageName;\n filesList: string[];\n enabled: boolean;\n\n shouldExtract?: (text: string) => boolean;\n configuration: IntlayerConfig;\n /**\n * Callback invoked for each extracted dictionary key/content pair.\n * Used by `getExtractPluginOptions` to write dictionaries to disk.\n * May be async — the plugin will fire-and-forget (Babel transforms are sync).\n */\n onExtract?: (result: ExtractResult) => void | Promise<void>;\n /**\n * Defines the output files path.\n */\n output?: FilePathPattern;\n};\n\ntype State = PluginPass & { opts: ExtractPluginOptions };\n\n/**\n * Babel plugin that extracts translatable content from source files and\n * injects Intlayer hooks (`useIntlayer` / `getIntlayer`) automatically.\n *\n * Designed for use with Babel-based build tools such as Next.js and Webpack.\n *\n * @example babel.config.js\n * ```js\n * const { intlayerExtractBabelPlugin, getExtractPluginOptions } = require('@intlayer/babel');\n * module.exports = {\n * presets: ['next/babel'],\n * plugins: [\n * [intlayerExtractBabelPlugin, getExtractPluginOptions()],\n * ],\n * };\n * ```\n */\nexport const intlayerExtractBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n return {\n name: 'babel-plugin-intlayer-extract',\n\n visitor: {\n Program: {\n enter(programPath, state) {\n const opts = state.opts;\n\n // Merge plugin options with the unified compiler config\n const isEnabled = opts.enabled;\n\n if (isEnabled === false) return;\n\n const filename = state.file.opts.filename;\n\n if (!filename) return;\n\n if (opts.filesList && !opts.filesList.includes(filename)) return;\n\n const fileCode: string = state.file.code ?? '';\n if (!fileCode) return;\n\n const appLogger = getAppLogger(opts.configuration);\n const packageName = opts.packageName ?? detectPackageName(filename);\n\n const { saveComponents } = opts.configuration.compiler;\n\n const result = extractContentSync(filename, packageName, {\n configuration: opts.configuration,\n code: fileCode,\n onExtract: (extractResult: {\n key: string;\n content: Record<string, string>;\n }) => {\n if (opts.onExtract) {\n opts.onExtract({\n dictionaryKey: extractResult.key,\n filePath: filename,\n content: extractResult.content,\n locale: opts.configuration.internationalization.defaultLocale,\n });\n }\n },\n declarationOnly: !saveComponents,\n });\n\n if (!result) return;\n\n const { transformedCode: modifiedCode } = result;\n\n if (!modifiedCode) return;\n\n // Replace the Babel AST with the transformed code by re-parsing it.\n // This lets Babel serialise the injected hooks/imports through its\n // own code generator, preserving compatibility with other plugins.\n try {\n const newAst = parse(modifiedCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n programPath.node.body = newAst.program.body;\n programPath.node.directives = newAst.program.directives;\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted content from ${colorizePath(relative(opts.configuration.system.baseDir, filename))}`,\n { level: 'debug' }\n );\n } catch (error) {\n appLogger(\n [\n `Failed to parse transformed code for ${colorizePath(relative(opts.configuration.system.baseDir, filename))}:`,\n error,\n ],\n { level: 'error' }\n );\n }\n },\n },\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,8BAA8B,WAEnB;CACtB,OAAO;EACL,MAAM;EAEN,SAAS,EACP,SAAS,EACP,MAAM,aAAa,OAAO;GACxB,MAAM,OAAO,MAAM;GAKnB,IAFkB,KAAK,YAEL,OAAO;GAEzB,MAAM,WAAW,MAAM,KAAK,KAAK;GAEjC,IAAI,CAAC,UAAU;GAEf,IAAI,KAAK,aAAa,CAAC,KAAK,UAAU,SAAS,QAAQ,GAAG;GAE1D,MAAM,WAAmB,MAAM,KAAK,QAAQ;GAC5C,IAAI,CAAC,UAAU;GAEf,MAAM,YAAY,aAAa,KAAK,aAAa;GACjD,MAAM,cAAc,KAAK,eAAe,kBAAkB,QAAQ;GAElE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAAS,mBAAmB,UAAU,aAAa;IACvD,eAAe,KAAK;IACpB,MAAM;IACN,YAAY,kBAGN;KACJ,IAAI,KAAK,WACP,KAAK,UAAU;MACb,eAAe,cAAc;MAC7B,UAAU;MACV,SAAS,cAAc;MACvB,QAAQ,KAAK,cAAc,qBAAqB;KAClD,CAAC;IAEL;IACA,iBAAiB,CAAC;GACpB,CAAC;GAED,IAAI,CAAC,QAAQ;GAEb,MAAM,EAAE,iBAAiB,iBAAiB;GAE1C,IAAI,CAAC,cAAc;GAKnB,IAAI;IACF,MAAM,SAAS,MAAM,cAAc;KACjC,YAAY;KACZ,SAAS,CAAC,OAAO,YAAY;IAC/B,CAAC;IAED,YAAY,KAAK,OAAO,OAAO,QAAQ;IACvC,YAAY,KAAK,aAAa,OAAO,QAAQ;IAE7C,UACE,GAAG,SAAS,aAAa,WAAW,SAAS,EAAE,0BAA0B,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,KAC3I,EAAE,OAAO,QAAQ,CACnB;GACF,SAAS,OAAO;IACd,UACE,CACE,wCAAwC,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,EAAE,IAC5G,KACF,GACA,EAAE,OAAO,QAAQ,CACnB;GACF;EACF,EACF,EACF;CACF;AACF"}
1
+ {"version":3,"file":"babel-plugin-intlayer-extract.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { PluginObj, PluginPass } from '@babel/core';\nimport { parse } from '@babel/parser';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport { extractContentSync } from './extractContent/extractContent';\nimport type { PackageName } from './extractContent/utils/constants';\nimport { detectPackageName } from './extractContent/utils/detectPackageName';\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: Record<string, string>;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n packageName?: PackageName;\n filesList: string[];\n enabled: boolean;\n\n shouldExtract?: (text: string) => boolean;\n configuration: IntlayerConfig;\n /**\n * Callback invoked for each extracted dictionary key/content pair.\n * Used by `getExtractPluginOptions` to write dictionaries to disk.\n * May be async — the plugin will fire-and-forget (Babel transforms are sync).\n */\n onExtract?: (result: ExtractResult) => void | Promise<void>;\n /**\n * Defines the output files path.\n */\n output?: FilePathPattern;\n};\n\ntype State = PluginPass & { opts: ExtractPluginOptions };\n\n/**\n * Babel plugin that extracts translatable content from source files and\n * injects Intlayer hooks (`useIntlayer` / `getIntlayer`) automatically.\n *\n * Designed for use with Babel-based build tools such as Next.js and Webpack.\n *\n * @example babel.config.js\n * ```js\n * const { intlayerExtractBabelPlugin, getExtractPluginOptions } = require('@intlayer/babel');\n * module.exports = {\n * presets: ['next/babel'],\n * plugins: [\n * [intlayerExtractBabelPlugin, getExtractPluginOptions()],\n * ],\n * };\n * ```\n */\nexport const intlayerExtractBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n return {\n name: 'babel-plugin-intlayer-extract',\n\n visitor: {\n Program: {\n enter(programPath, state) {\n const opts = state.opts;\n\n // Merge plugin options with the unified compiler config\n const isEnabled = opts.enabled;\n\n if (isEnabled === false) return;\n\n const filename = state.file.opts.filename;\n\n if (!filename) return;\n\n // Compare as POSIX paths (babel filenames use OS separators on Windows).\n if (\n opts.filesList &&\n !opts.filesList.map(normalizePath).includes(normalizePath(filename))\n ) {\n return;\n }\n\n const fileCode: string = state.file.code ?? '';\n if (!fileCode) return;\n\n const appLogger = getAppLogger(opts.configuration);\n const packageName = opts.packageName ?? detectPackageName(filename);\n\n const { saveComponents } = opts.configuration.compiler;\n\n const result = extractContentSync(filename, packageName, {\n configuration: opts.configuration,\n code: fileCode,\n onExtract: (extractResult: {\n key: string;\n content: Record<string, string>;\n }) => {\n if (opts.onExtract) {\n opts.onExtract({\n dictionaryKey: extractResult.key,\n filePath: filename,\n content: extractResult.content,\n locale: opts.configuration.internationalization.defaultLocale,\n });\n }\n },\n declarationOnly: !saveComponents,\n });\n\n if (!result) return;\n\n const { transformedCode: modifiedCode } = result;\n\n if (!modifiedCode) return;\n\n // Replace the Babel AST with the transformed code by re-parsing it.\n // This lets Babel serialise the injected hooks/imports through its\n // own code generator, preserving compatibility with other plugins.\n try {\n const newAst = parse(modifiedCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n programPath.node.body = newAst.program.body;\n programPath.node.directives = newAst.program.directives;\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted content from ${colorizePath(relative(opts.configuration.system.baseDir, filename))}`,\n { level: 'debug' }\n );\n } catch (error) {\n appLogger(\n [\n `Failed to parse transformed code for ${colorizePath(relative(opts.configuration.system.baseDir, filename))}:`,\n error,\n ],\n { level: 'error' }\n );\n }\n },\n },\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAa,8BAA8B,WAEnB;AACtB,QAAO;EACL,MAAM;EAEN,SAAS,EACP,SAAS,EACP,MAAM,aAAa,OAAO;GACxB,MAAM,OAAO,MAAM;AAKnB,OAFkB,KAAK,YAEL,MAAO;GAEzB,MAAM,WAAW,MAAM,KAAK,KAAK;AAEjC,OAAI,CAAC,SAAU;AAGf,OACE,KAAK,aACL,CAAC,KAAK,UAAU,IAAI,cAAc,CAAC,SAAS,cAAc,SAAS,CAAC,CAEpE;GAGF,MAAM,WAAmB,MAAM,KAAK,QAAQ;AAC5C,OAAI,CAAC,SAAU;GAEf,MAAM,YAAY,aAAa,KAAK,cAAc;GAClD,MAAM,cAAc,KAAK,eAAe,kBAAkB,SAAS;GAEnE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAAS,mBAAmB,UAAU,aAAa;IACvD,eAAe,KAAK;IACpB,MAAM;IACN,YAAY,kBAGN;AACJ,SAAI,KAAK,UACP,MAAK,UAAU;MACb,eAAe,cAAc;MAC7B,UAAU;MACV,SAAS,cAAc;MACvB,QAAQ,KAAK,cAAc,qBAAqB;MACjD,CAAC;;IAGN,iBAAiB,CAAC;IACnB,CAAC;AAEF,OAAI,CAAC,OAAQ;GAEb,MAAM,EAAE,iBAAiB,iBAAiB;AAE1C,OAAI,CAAC,aAAc;AAKnB,OAAI;IACF,MAAM,SAAS,MAAM,cAAc;KACjC,YAAY;KACZ,SAAS,CAAC,OAAO,aAAa;KAC/B,CAAC;AAEF,gBAAY,KAAK,OAAO,OAAO,QAAQ;AACvC,gBAAY,KAAK,aAAa,OAAO,QAAQ;AAE7C,cACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,0BAA0B,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,SAAS,CAAC,IAC5I,EAAE,OAAO,SAAS,CACnB;YACM,OAAO;AACd,cACE,CACE,wCAAwC,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,SAAS,CAAC,CAAC,IAC5G,MACD,EACD,EAAE,OAAO,SAAS,CACnB;;KAGN,EACF;EACF"}
@@ -106,6 +106,10 @@ const walkRenameChain = (babelTypes, startPath, currentRenameMap) => {
106
106
  refPath = parentPath;
107
107
  renameMap = renameEntry.children;
108
108
  }
109
+ return {
110
+ finalPath: refPath,
111
+ finalRenameMap: renameMap
112
+ };
109
113
  };
110
114
  /**
111
115
  * Walks an object-destructuring assignment whose right-hand side is `refPath`,
@@ -139,8 +143,8 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
139
143
  const localVarName = property.value.name;
140
144
  const localVarBinding = refPath.scope.getBinding(localVarName);
141
145
  if (localVarBinding) for (const nestedRefPath of localVarBinding.referencePaths) {
142
- walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);
143
- walkObjectDestructuring(babelTypes, nestedRefPath, renameEntry.children);
146
+ const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);
147
+ walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
144
148
  }
145
149
  }
146
150
  }
@@ -213,15 +217,16 @@ const makeFieldRenameBabelPlugin = (pruneContext) => ({ types: babelTypes }) =>
213
217
  if (renameEntry.children.size > 0 && babelTypes.isIdentifier(property.value)) {
214
218
  const localVarBinding = callExpressionPath.scope.getBinding(property.value.name);
215
219
  if (localVarBinding) for (const refPath of localVarBinding.referencePaths) {
216
- walkRenameChain(babelTypes, refPath, renameEntry.children);
217
- walkObjectDestructuring(babelTypes, refPath, renameEntry.children);
220
+ const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, refPath, renameEntry.children);
221
+ walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
218
222
  }
219
223
  }
220
224
  }
221
225
  return;
222
226
  }
223
227
  if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === callExpressionPath.node) {
224
- walkRenameChain(babelTypes, callExpressionPath, fieldRenameMap);
228
+ const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, callExpressionPath, fieldRenameMap);
229
+ walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
225
230
  return;
226
231
  }
227
232
  if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
@@ -229,14 +234,14 @@ const makeFieldRenameBabelPlugin = (pruneContext) => ({ types: babelTypes }) =>
229
234
  const variableBinding = callExpressionPath.scope.getBinding(variableName);
230
235
  if (!variableBinding) return;
231
236
  for (const variableReferencePath of variableBinding.referencePaths) {
232
- walkRenameChain(babelTypes, variableReferencePath, fieldRenameMap);
233
- walkObjectDestructuring(babelTypes, variableReferencePath, fieldRenameMap);
237
+ const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, variableReferencePath, fieldRenameMap);
238
+ walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
234
239
  const refParent = variableReferencePath.parent;
235
240
  if ((babelTypes.isCallExpression(refParent) || babelTypes.isOptionalCallExpression(refParent)) && refParent.callee === variableReferencePath.node) {
236
241
  const callPath = variableReferencePath.parentPath;
237
242
  if (callPath) {
238
- walkRenameChain(babelTypes, callPath, fieldRenameMap);
239
- walkObjectDestructuring(babelTypes, callPath, fieldRenameMap);
243
+ const { finalPath: signalFinalPath, finalRenameMap: signalFinalRenameMap } = walkRenameChain(babelTypes, callPath, fieldRenameMap);
244
+ walkObjectDestructuring(babelTypes, signalFinalPath, signalFinalRenameMap);
240
245
  }
241
246
  }
242
247
  }
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-field-rename.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"sourcesContent":["import type { NodePath, PluginObj } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\n\n// ── Field-name helpers ────────────────────────────────────────────────────────\n\n/**\n * Intlayer internal property names that must never be used as short-name\n * targets. These appear inside content-node values (e.g. `{ nodeType:\n * \"translation\", … }`) and are read by the intlayer runtime.\n */\nconst RESERVED_CONTENT_FIELD_NAMES = new Set(['nodeType']);\n\n/**\n * Converts a zero-based index to a short alphabetic identifier.\n * 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …\n */\nexport const generateShortFieldName = (index: number): string => {\n const ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n const remainder = index % ALPHABET.length;\n const quotient = Math.floor(index / ALPHABET.length);\n\n return quotient === 0 && ALPHABET[remainder]\n ? ALPHABET[remainder]\n : generateShortFieldName(quotient - 1) + ALPHABET[remainder];\n};\n\n/**\n * Recursively builds a `NestedRenameMap` from a compiled dictionary content\n * value by traversing the intlayer node structure.\n *\n * Rules:\n * - If the value has `nodeType: 'translation'`, user-defined fields live\n * inside `translation[locale]`. Recurse into the first locale's value.\n * - All other intlayer runtime nodes (enumeration, condition, gender, …) are\n * treated as leaves — their internal keys must never be renamed.\n * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\n): NestedRenameMap => {\n if (\n !contentValue ||\n typeof contentValue !== 'object' ||\n Array.isArray(contentValue)\n ) {\n return new Map();\n }\n\n const record = contentValue as Record<string, unknown>;\n\n // Any object with `nodeType: string` is an intlayer runtime node.\n if (typeof record.nodeType === 'string') {\n // Translation node: user-defined fields live inside translation[locale].\n if (\n record.nodeType === 'translation' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const firstLocaleValue = Object.values(\n record.translation as Record<string, unknown>\n )[0];\n return buildNestedRenameMapFromContent(firstLocaleValue);\n }\n\n // Enumeration node: user-defined keys live inside enumeration.\n if (\n record.nodeType === 'enumeration' &&\n record.enumeration &&\n typeof record.enumeration === 'object' &&\n !Array.isArray(record.enumeration)\n ) {\n const values = Object.values(\n record.enumeration as Record<string, unknown>\n );\n if (values.length > 0) {\n return buildNestedRenameMapFromContent(values[0]);\n }\n }\n\n // All other intlayer nodes (pluralization, conditions, etc.) resolve to a\n // single value at runtime – return an empty map so they are treated\n // as leaves unless they contain nested user records.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\n renameMap.set(key, { shortName: generateShortFieldName(i), children });\n }\n\n return renameMap;\n};\n\n// ── Field-rename Babel plugin ─────────────────────────────────────────────────\n\n/**\n * Walks a MemberExpression chain starting from `startPath`, renaming each\n * property found in `currentRenameMap` at the corresponding nesting level.\n *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): void => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const parentPath = refPath.parentPath;\n if (!parentPath) break;\n\n const parentNode = parentPath.node;\n\n if (\n (!babelTypes.isMemberExpression(parentNode) &&\n !babelTypes.isOptionalMemberExpression(parentNode)) ||\n (parentNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n break;\n }\n\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\n let fieldName: string | undefined;\n\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n fieldName = memberNode.property.name;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n fieldName = memberNode.property.value;\n } else {\n break; // dynamic computed key – stop\n }\n\n const renameEntry = renameMap.get(fieldName);\n if (!renameEntry) break; // not in map – stop\n\n // Apply the rename\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n memberNode.property.name = renameEntry.shortName;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n memberNode.property.value = renameEntry.shortName;\n }\n\n refPath = parentPath;\n renameMap = renameEntry.children;\n }\n};\n\n/**\n * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key the function recursively walks references to the\n * newly-bound local variable, calling both `walkRenameChain` (for subsequent\n * member-access chains like `validationErrors.invalidUrl`) and itself (for\n * further levels of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n for (const property of (parentNode.id as BabelTypes.ObjectPattern)\n .properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const renameEntry = renameMap.get(keyName);\n if (!renameEntry) continue;\n\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n }\n property.key = babelTypes.identifier(renameEntry.shortName);\n\n // Recursively walk references to the local variable bound by this key.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarName = (property.value as BabelTypes.Identifier).name;\n const localVarBinding = refPath.scope.getBinding(localVarName);\n if (localVarBinding) {\n for (const nestedRefPath of localVarBinding.referencePaths) {\n walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);\n walkObjectDestructuring(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n }\n }\n }\n }\n};\n\n/**\n * Creates a Babel plugin that rewrites dictionary content field accesses in\n * source files to their short aliases defined in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n *\n * Handled patterns (mirrors the usage analyser):\n *\n * const { fieldA, fieldB } = useIntlayer('key')\n * → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')\n *\n * useIntlayer('key').fieldA\n * → useIntlayer('key').shortA\n *\n * const result = useIntlayer('key'); result.fieldA\n * → const result = useIntlayer('key'); result.shortA\n *\n * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\n *\n * This plugin must run in a separate `transformAsync` pass **before**\n * `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`\n * with `useDictionary`, erasing the dictionary-key information needed here.\n */\nexport const makeFieldRenameBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-field-rename',\n visitor: {\n Program: {\n exit: (programPath) => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;\n\n // Collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Visit all useIntlayer / getIntlayer call-sites and rename field accesses\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1 &&\n firstArgument.quasis[0]\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return;\n\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (!fieldRenameMap || fieldRenameMap.size === 0) return;\n\n const parentNode = callExpressionPath.parent;\n\n // ── Case 1: const { fieldA, fieldB } = useIntlayer('key') ────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const renameEntry = fieldRenameMap.get(keyName);\n if (!renameEntry) continue;\n\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n property.key = babelTypes.identifier(renameEntry.shortName);\n } else {\n property.key = babelTypes.identifier(renameEntry.shortName);\n }\n\n // Walk nested member accesses and secondary destructurings\n // on the local variable.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarBinding = callExpressionPath.scope.getBinding(\n (property.value as BabelTypes.Identifier).name\n );\n if (localVarBinding) {\n for (const refPath of localVarBinding.referencePaths) {\n walkRenameChain(\n babelTypes,\n refPath,\n renameEntry.children\n );\n walkObjectDestructuring(\n babelTypes,\n refPath,\n renameEntry.children\n );\n }\n }\n }\n }\n return;\n }\n\n // ── Case 2: useIntlayer('key').fieldA.nested ─────────────────────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n walkRenameChain(babelTypes, callExpressionPath, fieldRenameMap);\n return;\n }\n\n // ── Case 3: const result = useIntlayer('key'); result.fieldA ─────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding =\n callExpressionPath.scope.getBinding(variableName);\n if (!variableBinding) return;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n // Direct access: result.fieldA or const { fieldA } = result\n walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n walkObjectDestructuring(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n walkRenameChain(babelTypes, callPath, fieldRenameMap);\n walkObjectDestructuring(\n babelTypes,\n callPath,\n fieldRenameMap\n );\n }\n }\n }\n }\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"babel-plugin-intlayer-field-rename.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"sourcesContent":["import type { NodePath, PluginObj } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\n\n// ── Field-name helpers ────────────────────────────────────────────────────────\n\n/**\n * Intlayer internal property names that must never be used as short-name\n * targets. These appear inside content-node values (e.g. `{ nodeType:\n * \"translation\", … }`) and are read by the intlayer runtime.\n */\nconst RESERVED_CONTENT_FIELD_NAMES = new Set(['nodeType']);\n\n/**\n * Converts a zero-based index to a short alphabetic identifier.\n * 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …\n */\nexport const generateShortFieldName = (index: number): string => {\n const ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n const remainder = index % ALPHABET.length;\n const quotient = Math.floor(index / ALPHABET.length);\n\n return quotient === 0 && ALPHABET[remainder]\n ? ALPHABET[remainder]\n : generateShortFieldName(quotient - 1) + ALPHABET[remainder];\n};\n\n/**\n * Recursively builds a `NestedRenameMap` from a compiled dictionary content\n * value by traversing the intlayer node structure.\n *\n * Rules:\n * - If the value has `nodeType: 'translation'`, user-defined fields live\n * inside `translation[locale]`. Recurse into the first locale's value.\n * - All other intlayer runtime nodes (enumeration, condition, gender, …) are\n * treated as leaves — their internal keys must never be renamed.\n * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\n): NestedRenameMap => {\n if (\n !contentValue ||\n typeof contentValue !== 'object' ||\n Array.isArray(contentValue)\n ) {\n return new Map();\n }\n\n const record = contentValue as Record<string, unknown>;\n\n // Any object with `nodeType: string` is an intlayer runtime node.\n if (typeof record.nodeType === 'string') {\n // Translation node: user-defined fields live inside translation[locale].\n if (\n record.nodeType === 'translation' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const firstLocaleValue = Object.values(\n record.translation as Record<string, unknown>\n )[0];\n return buildNestedRenameMapFromContent(firstLocaleValue);\n }\n\n // Enumeration node: user-defined keys live inside enumeration.\n if (\n record.nodeType === 'enumeration' &&\n record.enumeration &&\n typeof record.enumeration === 'object' &&\n !Array.isArray(record.enumeration)\n ) {\n const values = Object.values(\n record.enumeration as Record<string, unknown>\n );\n if (values.length > 0) {\n return buildNestedRenameMapFromContent(values[0]);\n }\n }\n\n // All other intlayer nodes (pluralization, conditions, etc.) resolve to a\n // single value at runtime – return an empty map so they are treated\n // as leaves unless they contain nested user records.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\n renameMap.set(key, { shortName: generateShortFieldName(i), children });\n }\n\n return renameMap;\n};\n\n// ── Field-rename Babel plugin ─────────────────────────────────────────────────\n\n/**\n * Walks a MemberExpression chain starting from `startPath`, renaming each\n * property found in `currentRenameMap` at the corresponding nesting level.\n *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): {\n finalPath: NodePath<BabelTypes.Node>;\n finalRenameMap: NestedRenameMap;\n} => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const parentPath = refPath.parentPath;\n if (!parentPath) break;\n\n const parentNode = parentPath.node;\n\n if (\n (!babelTypes.isMemberExpression(parentNode) &&\n !babelTypes.isOptionalMemberExpression(parentNode)) ||\n (parentNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n break;\n }\n\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\n let fieldName: string | undefined;\n\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n fieldName = memberNode.property.name;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n fieldName = memberNode.property.value;\n } else {\n break; // dynamic computed key – stop\n }\n\n const renameEntry = renameMap.get(fieldName);\n if (!renameEntry) break; // not in map – stop\n\n // Apply the rename\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n memberNode.property.name = renameEntry.shortName;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n memberNode.property.value = renameEntry.shortName;\n }\n\n refPath = parentPath;\n renameMap = renameEntry.children;\n }\n\n return { finalPath: refPath, finalRenameMap: renameMap };\n};\n\n/**\n * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key the function recursively walks references to the\n * newly-bound local variable, calling both `walkRenameChain` (for subsequent\n * member-access chains like `validationErrors.invalidUrl`) and itself (for\n * further levels of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n for (const property of (parentNode.id as BabelTypes.ObjectPattern)\n .properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const renameEntry = renameMap.get(keyName);\n if (!renameEntry) continue;\n\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n }\n property.key = babelTypes.identifier(renameEntry.shortName);\n\n // Recursively walk references to the local variable bound by this key.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarName = (property.value as BabelTypes.Identifier).name;\n const localVarBinding = refPath.scope.getBinding(localVarName);\n if (localVarBinding) {\n for (const nestedRefPath of localVarBinding.referencePaths) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n }\n }\n }\n }\n};\n\n/**\n * Creates a Babel plugin that rewrites dictionary content field accesses in\n * source files to their short aliases defined in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n *\n * Handled patterns (mirrors the usage analyser):\n *\n * const { fieldA, fieldB } = useIntlayer('key')\n * → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')\n *\n * useIntlayer('key').fieldA\n * → useIntlayer('key').shortA\n *\n * const result = useIntlayer('key'); result.fieldA\n * → const result = useIntlayer('key'); result.shortA\n *\n * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\n *\n * This plugin must run in a separate `transformAsync` pass **before**\n * `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`\n * with `useDictionary`, erasing the dictionary-key information needed here.\n */\nexport const makeFieldRenameBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\n name: 'intlayer-field-rename',\n visitor: {\n Program: {\n exit: (programPath) => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;\n\n // Collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Visit all useIntlayer / getIntlayer call-sites and rename field accesses\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1 &&\n firstArgument.quasis[0]\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return;\n\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (!fieldRenameMap || fieldRenameMap.size === 0) return;\n\n const parentNode = callExpressionPath.parent;\n\n // ── Case 1: const { fieldA, fieldB } = useIntlayer('key') ────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const renameEntry = fieldRenameMap.get(keyName);\n if (!renameEntry) continue;\n\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n property.key = babelTypes.identifier(renameEntry.shortName);\n } else {\n property.key = babelTypes.identifier(renameEntry.shortName);\n }\n\n // Walk nested member accesses and secondary destructurings\n // on the local variable.\n if (\n renameEntry.children.size > 0 &&\n babelTypes.isIdentifier(property.value)\n ) {\n const localVarBinding = callExpressionPath.scope.getBinding(\n (property.value as BabelTypes.Identifier).name\n );\n if (localVarBinding) {\n for (const refPath of localVarBinding.referencePaths) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n refPath,\n renameEntry.children\n );\n walkObjectDestructuring(\n babelTypes,\n finalPath,\n finalRenameMap\n );\n }\n }\n }\n }\n return;\n }\n\n // ── Case 2: useIntlayer('key').fieldA.nested ─────────────────────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n callExpressionPath,\n fieldRenameMap\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n return;\n }\n\n // ── Case 3: const result = useIntlayer('key'); result.fieldA ─────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding =\n callExpressionPath.scope.getBinding(variableName);\n if (!variableBinding) return;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n // Direct access: result.fieldA or const { fieldA } = result\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n walkObjectDestructuring(\n babelTypes,\n finalPath,\n finalRenameMap\n );\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n const {\n finalPath: signalFinalPath,\n finalRenameMap: signalFinalRenameMap,\n } = walkRenameChain(babelTypes, callPath, fieldRenameMap);\n walkObjectDestructuring(\n babelTypes,\n signalFinalPath,\n signalFinalRenameMap\n );\n }\n }\n }\n }\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { makeFieldRenameBabelPlugin } from "./babel-plugin-intlayer-field-rename.mjs";
2
+ import { INTLAYER_USAGE_REGEX } from "./transformers.mjs";
3
+ import { getSharedPruneContext } from "./babel-plugin-intlayer-purge.mjs";
4
+
5
+ //#region src/babel-plugin-intlayer-minify.ts
6
+ /**
7
+ * Babel plugin that rewrites component source files to use the short
8
+ * alphabetic aliases assigned to dictionary content fields during the
9
+ * minification pass (e.g. `content.title` → `content.a`).
10
+ *
11
+ * This plugin relies on the field-rename map built by
12
+ * {@link intlayerPurgeBabelPlugin} during its `pre()` hook. Both plugins
13
+ * share the same module-level {@link PruneContext} via
14
+ * {@link getSharedPruneContext}, so **`intlayerPurgeBabelPlugin` must be
15
+ * listed before this plugin** in `babel.config.js` so that its `pre()` runs
16
+ * first and the shared context is populated before this plugin's
17
+ * `Program.exit` visitor fires.
18
+ *
19
+ * All option values must be pre-resolved via {@link getMinifyPluginOptions}.
20
+ *
21
+ * @example
22
+ * ```js
23
+ * // babel.config.js
24
+ * const {
25
+ * intlayerPurgeBabelPlugin,
26
+ * intlayerMinifyBabelPlugin,
27
+ * intlayerOptimizeBabelPlugin,
28
+ * getPurgePluginOptions,
29
+ * getMinifyPluginOptions,
30
+ * getOptimizePluginOptions,
31
+ * } = require("@intlayer/babel");
32
+ *
33
+ * module.exports = {
34
+ * presets: ["next/babel"],
35
+ * plugins: [
36
+ * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],
37
+ * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],
38
+ * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],
39
+ * ],
40
+ * };
41
+ * ```
42
+ *
43
+ * @remarks
44
+ * - This plugin is a no-op when `minify` is `false`, `optimize` is `false`,
45
+ * or `editorEnabled` is `true`.
46
+ * - Source-code renames must run **before** {@link intlayerOptimizeBabelPlugin}
47
+ * because the optimize pass replaces `useIntlayer` calls with
48
+ * `useDictionary`, erasing the dictionary-key information needed to look up
49
+ * the rename map.
50
+ */
51
+ const intlayerMinifyBabelPlugin = (babel) => {
52
+ /**
53
+ * The field-rename `Program.exit` handler extracted from
54
+ * {@link makeFieldRenameBabelPlugin}. Resolved once per plugin-instance
55
+ * (i.e. once per babel.config.js registration, not once per file).
56
+ */
57
+ let fieldRenameExitVisitor = null;
58
+ /** The `baseDir` for which the visitor was last resolved. */
59
+ let resolvedBaseDir = null;
60
+ return {
61
+ name: "intlayer-minify",
62
+ pre() {
63
+ const { baseDir, minify, optimize, editorEnabled } = this.opts;
64
+ if (!minify || optimize === false || editorEnabled) return;
65
+ if (resolvedBaseDir === baseDir && fieldRenameExitVisitor !== null) return;
66
+ const pruneContext = getSharedPruneContext(baseDir);
67
+ if (!pruneContext || pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;
68
+ resolvedBaseDir = baseDir;
69
+ const programVisitor = makeFieldRenameBabelPlugin(pruneContext)(babel).visitor.Program;
70
+ if (programVisitor && typeof programVisitor === "object" && "exit" in programVisitor && typeof programVisitor.exit === "function") fieldRenameExitVisitor = programVisitor.exit;
71
+ },
72
+ visitor: { Program: { exit(programPath, state) {
73
+ if (!fieldRenameExitVisitor) return;
74
+ if (!INTLAYER_USAGE_REGEX.test(state.file.code)) return;
75
+ fieldRenameExitVisitor(programPath);
76
+ } } }
77
+ };
78
+ };
79
+
80
+ //#endregion
81
+ export { intlayerMinifyBabelPlugin };
82
+ //# sourceMappingURL=babel-plugin-intlayer-minify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babel-plugin-intlayer-minify.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-minify.ts"],"sourcesContent":["import type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport { getSharedPruneContext } from './babel-plugin-intlayer-purge';\nimport type { PruneContext } from './babel-plugin-intlayer-usage-analyzer';\nimport { INTLAYER_USAGE_REGEX } from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerMinifyBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getMinifyPluginOptions}) so the plugin does not read the intlayer\n * configuration file on every file transform.\n */\nexport type MinifyPluginOptions = {\n /**\n * Absolute path to the project root. Used to look up the shared\n * {@link PruneContext} built by {@link intlayerPurgeBabelPlugin}.\n */\n baseDir: string;\n\n /**\n * When `true`, rewrite source-file property accesses to use the short\n * alphabetic aliases assigned during the minification pass\n * (e.g. `content.title` → `content.a`). Mirrors `build.minify`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. When explicitly `false`, the plugin is a\n * no-op. Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin is a no-op. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that rewrites component source files to use the short\n * alphabetic aliases assigned to dictionary content fields during the\n * minification pass (e.g. `content.title` → `content.a`).\n *\n * This plugin relies on the field-rename map built by\n * {@link intlayerPurgeBabelPlugin} during its `pre()` hook. Both plugins\n * share the same module-level {@link PruneContext} via\n * {@link getSharedPruneContext}, so **`intlayerPurgeBabelPlugin` must be\n * listed before this plugin** in `babel.config.js` so that its `pre()` runs\n * first and the shared context is populated before this plugin's\n * `Program.exit` visitor fires.\n *\n * All option values must be pre-resolved via {@link getMinifyPluginOptions}.\n *\n * @example\n * ```js\n * // babel.config.js\n * const {\n * intlayerPurgeBabelPlugin,\n * intlayerMinifyBabelPlugin,\n * intlayerOptimizeBabelPlugin,\n * getPurgePluginOptions,\n * getMinifyPluginOptions,\n * getOptimizePluginOptions,\n * } = require(\"@intlayer/babel\");\n *\n * module.exports = {\n * presets: [\"next/babel\"],\n * plugins: [\n * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],\n * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],\n * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n * ],\n * };\n * ```\n *\n * @remarks\n * - This plugin is a no-op when `minify` is `false`, `optimize` is `false`,\n * or `editorEnabled` is `true`.\n * - Source-code renames must run **before** {@link intlayerOptimizeBabelPlugin}\n * because the optimize pass replaces `useIntlayer` calls with\n * `useDictionary`, erasing the dictionary-key information needed to look up\n * the rename map.\n */\nexport const intlayerMinifyBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj => {\n /**\n * The field-rename `Program.exit` handler extracted from\n * {@link makeFieldRenameBabelPlugin}. Resolved once per plugin-instance\n * (i.e. once per babel.config.js registration, not once per file).\n */\n let fieldRenameExitVisitor:\n | ((programPath: NodePath<BabelTypes.Program>) => void)\n | null = null;\n\n /** The `baseDir` for which the visitor was last resolved. */\n let resolvedBaseDir: string | null = null;\n\n return {\n name: 'intlayer-minify',\n\n pre(this: PluginPass & { opts: MinifyPluginOptions }) {\n const { baseDir, minify, optimize, editorEnabled } = this.opts;\n\n if (!minify || optimize === false || editorEnabled) return;\n\n // Re-resolve when the baseDir changes (edge case in monorepos where the\n // same process handles multiple workspaces with different configs).\n if (resolvedBaseDir === baseDir && fieldRenameExitVisitor !== null)\n return;\n\n const pruneContext: PruneContext | null = getSharedPruneContext(baseDir);\n if (\n !pruneContext ||\n pruneContext.dictionaryKeyToFieldRenameMap.size === 0\n )\n return;\n\n resolvedBaseDir = baseDir;\n\n // Instantiate makeFieldRenameBabelPlugin and extract its Program.exit\n // handler so we can invoke it from our own visitor.\n const fieldRenamePlugin = makeFieldRenameBabelPlugin(pruneContext)(babel);\n const programVisitor = fieldRenamePlugin.visitor.Program;\n\n if (\n programVisitor &&\n typeof programVisitor === 'object' &&\n 'exit' in programVisitor &&\n typeof (\n programVisitor as {\n exit: (path: NodePath<BabelTypes.Program>) => void;\n }\n ).exit === 'function'\n ) {\n fieldRenameExitVisitor = (\n programVisitor as {\n exit: (path: NodePath<BabelTypes.Program>) => void;\n }\n ).exit;\n }\n },\n\n visitor: {\n Program: {\n exit(\n programPath: NodePath<BabelTypes.Program>,\n state: PluginPass\n ): void {\n if (!fieldRenameExitVisitor) return;\n if (!INTLAYER_USAGE_REGEX.test(state.file.code)) return;\n\n fieldRenameExitVisitor(programPath);\n },\n },\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,MAAa,6BAA6B,UAEzB;;;;;;CAMf,IAAI,yBAEO;;CAGX,IAAI,kBAAiC;AAErC,QAAO;EACL,MAAM;EAEN,MAAsD;GACpD,MAAM,EAAE,SAAS,QAAQ,UAAU,kBAAkB,KAAK;AAE1D,OAAI,CAAC,UAAU,aAAa,SAAS,cAAe;AAIpD,OAAI,oBAAoB,WAAW,2BAA2B,KAC5D;GAEF,MAAM,eAAoC,sBAAsB,QAAQ;AACxE,OACE,CAAC,gBACD,aAAa,8BAA8B,SAAS,EAEpD;AAEF,qBAAkB;GAKlB,MAAM,iBADoB,2BAA2B,aAAa,CAAC,MAC3B,CAAC,QAAQ;AAEjD,OACE,kBACA,OAAO,mBAAmB,YAC1B,UAAU,kBACV,OACE,eAGA,SAAS,WAEX,0BACE,eAGA;;EAIN,SAAS,EACP,SAAS,EACP,KACE,aACA,OACM;AACN,OAAI,CAAC,uBAAwB;AAC7B,OAAI,CAAC,qBAAqB,KAAK,MAAM,KAAK,KAAK,CAAE;AAEjD,0BAAuB,YAAY;KAEtC,EACF;EACF"}
@@ -45,6 +45,13 @@ const STATIC_IMPORT_FUNCTION = {
45
45
  };
46
46
  const DYNAMIC_IMPORT_FUNCTION = { useIntlayer: "useDictionaryDynamic" };
47
47
  /**
48
+ * Packages whose SSR dynamic helper should render synchronously with a static
49
+ * dictionary. Solid streaming SSR can hydrate static output reliably, while
50
+ * its dynamic resource path either suspends during hydration or serializes the
51
+ * full dictionary into HTML.
52
+ */
53
+ const PACKAGE_SSR_DYNAMIC_STATIC_FALLBACK = new Set(["solid-intlayer"]);
54
+ /**
48
55
  * Replicates the xxHash64 → Base-62 algorithm used by the SWC version
49
56
  * and prefixes an underscore so the generated identifiers never collide
50
57
  * with user-defined ones.
@@ -62,6 +69,13 @@ const computeImport = (fromFile, dictionariesDir, dynamicDictionariesDir, fetchD
62
69
  if (!rel.startsWith("./") && !rel.startsWith("../")) rel = `./${rel}`;
63
70
  return rel;
64
71
  };
72
+ const getKeyFromArgument = (arg, t) => {
73
+ if (arg && t.isStringLiteral(arg)) return arg.value;
74
+ if (arg && t.isTemplateLiteral(arg) && arg.expressions.length === 0 && arg.quasis.length === 1) return arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;
75
+ };
76
+ const isCallerName = (name) => CALLER_LIST.includes(name);
77
+ const isDynamicPackage = (packageName) => PACKAGE_LIST_DYNAMIC.includes(packageName);
78
+ const shouldUseStaticSsrDynamicFallback = (callerPackage, importMode, opts) => opts.isServer === true && importMode === "dynamic" && callerPackage !== void 0 && PACKAGE_SSR_DYNAMIC_STATIC_FALLBACK.has(callerPackage);
65
79
  /**
66
80
  * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.
67
81
  *
@@ -117,11 +131,11 @@ const computeImport = (fromFile, dictionariesDir, dynamicDictionariesDir, fetchD
117
131
  * const content2 = getIntlayer(_dicHash);
118
132
  * ```
119
133
  *
120
- * ### Live Mode (`importMode = "live"`)
134
+ * ### Fetch Mode (`importMode = "fetch"`)
121
135
  *
122
- * Uses live-based dictionary loading for remote dictionaries:
136
+ * Uses fetch-based dictionary loading for remote dictionaries:
123
137
  *
124
- * **Output if `dictionaryModeMap` includes the key with "live" value:**
138
+ * **Output if `dictionaryModeMap` includes the key with "fetch" value:**
125
139
  * ```ts
126
140
  * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };
127
141
  * import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';
@@ -132,7 +146,7 @@ const computeImport = (fromFile, dictionariesDir, dynamicDictionariesDir, fetchD
132
146
  * const content2 = getIntlayer(_dicHash);
133
147
  * ```
134
148
  *
135
- * > If `dictionaryModeMap` does not include the key with "live" value, the plugin will fallback to the dynamic impor
149
+ * > If `dictionaryModeMap` does not include the key with "fetch" value, the plugin will fallback to the dynamic import mode.
136
150
  *
137
151
  * ```ts
138
152
  * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };
@@ -152,6 +166,7 @@ const intlayerOptimizeBabelPlugin = (babel) => {
152
166
  this._newStaticImports = /* @__PURE__ */ new Map();
153
167
  this._newDynamicImports = /* @__PURE__ */ new Map();
154
168
  this._callerMap = /* @__PURE__ */ new Map();
169
+ this._callerPackageMap = /* @__PURE__ */ new Map();
155
170
  this._isIncluded = true;
156
171
  this._hasValidImport = false;
157
172
  this._isDictEntry = false;
@@ -160,9 +175,9 @@ const intlayerOptimizeBabelPlugin = (babel) => {
160
175
  this._isIncluded = false;
161
176
  return;
162
177
  }
163
- const filename = this.file.opts.filename;
178
+ const filename = this.file.opts.filename ? normalizePath(this.file.opts.filename) : void 0;
164
179
  if (this.opts.filesList && filename) {
165
- if (!this.opts.filesList.includes(filename)) {
180
+ if (!this.opts.filesList.map(normalizePath).includes(filename)) {
166
181
  this._isIncluded = false;
167
182
  return;
168
183
  }
@@ -170,8 +185,9 @@ const intlayerOptimizeBabelPlugin = (babel) => {
170
185
  },
171
186
  visitor: { Program: {
172
187
  enter(programPath, state) {
173
- const filename = state.file.opts.filename;
174
- if (state.opts.replaceDictionaryEntry && filename === state.opts.dictionariesEntryPath) {
188
+ const filename = state.file.opts.filename ? normalizePath(state.file.opts.filename) : void 0;
189
+ const dictionariesEntryPath = state.opts.dictionariesEntryPath ? normalizePath(state.opts.dictionariesEntryPath) : void 0;
190
+ if (state.opts.replaceDictionaryEntry && filename === dictionariesEntryPath) {
175
191
  state._isDictEntry = true;
176
192
  programPath.traverse({
177
193
  ImportDeclaration(path) {
@@ -194,30 +210,46 @@ const intlayerOptimizeBabelPlugin = (babel) => {
194
210
  exit(programPath, state) {
195
211
  if (state._isDictEntry) return;
196
212
  if (!state._isIncluded) return;
197
- let fileHasDynamicCall = false;
213
+ programPath.traverse({ ImportDeclaration(path) {
214
+ const src = path.node.source.value;
215
+ if (!PACKAGE_LIST.includes(src)) return;
216
+ state._hasValidImport = true;
217
+ for (const spec of path.node.specifiers) {
218
+ if (!t.isImportSpecifier(spec)) continue;
219
+ const importedName = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
220
+ if (isCallerName(importedName)) {
221
+ state._callerMap?.set(spec.local.name, importedName);
222
+ state._callerPackageMap?.set(spec.local.name, src);
223
+ }
224
+ }
225
+ } });
226
+ const packagesWithDynamicCall = /* @__PURE__ */ new Set();
227
+ const packagesWithFetchCall = /* @__PURE__ */ new Set();
198
228
  programPath.traverse({ CallExpression(path) {
199
229
  const callee = path.node.callee;
200
230
  if (!t.isIdentifier(callee)) return;
201
231
  if (state._callerMap?.get(callee.name) !== "useIntlayer") return;
202
- const arg = path.node.arguments[0];
203
- let key;
204
- if (arg && t.isStringLiteral(arg)) key = arg.value;
205
- else if (arg && t.isTemplateLiteral(arg) && arg.expressions.length === 0 && arg.quasis.length === 1) key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;
232
+ const callerPackage = state._callerPackageMap?.get(callee.name);
233
+ if (!callerPackage) return;
234
+ const key = getKeyFromArgument(path.node.arguments[0], t);
206
235
  if (!key) return;
207
236
  const dictionaryOverrideMode = state.opts.dictionaryModeMap?.[key];
208
- if (dictionaryOverrideMode === "dynamic" || dictionaryOverrideMode === "fetch") fileHasDynamicCall = true;
237
+ if (dictionaryOverrideMode === "dynamic") packagesWithDynamicCall.add(callerPackage);
238
+ else if (dictionaryOverrideMode === "fetch") packagesWithFetchCall.add(callerPackage);
209
239
  } });
210
240
  programPath.traverse({
211
241
  ImportDeclaration(path) {
212
242
  const src = path.node.source.value;
213
243
  if (!PACKAGE_LIST.includes(src)) return;
214
- state._hasValidImport = true;
215
244
  for (const spec of path.node.specifiers) {
216
245
  if (!t.isImportSpecifier(spec)) continue;
217
246
  const importedName = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
218
- if (CALLER_LIST.includes(importedName)) state._callerMap?.set(spec.local.name, importedName);
247
+ if (!isCallerName(importedName)) continue;
219
248
  const importMode = state.opts.importMode;
220
- const shouldUseDynamicHelpers = (importMode === "dynamic" || importMode === "fetch" || fileHasDynamicCall) && PACKAGE_LIST_DYNAMIC.includes(src);
249
+ const packageHasDynamicCall = packagesWithDynamicCall.has(src);
250
+ const packageHasFetchCall = packagesWithFetchCall.has(src);
251
+ const shouldUseStaticFallback = !packageHasFetchCall && shouldUseStaticSsrDynamicFallback(src, importMode === "dynamic" || packageHasDynamicCall ? "dynamic" : importMode, state.opts);
252
+ const shouldUseDynamicHelpers = isDynamicPackage(src) && (importMode === "fetch" || packageHasFetchCall || (importMode === "dynamic" || packageHasDynamicCall) && !shouldUseStaticFallback);
221
253
  if (shouldUseDynamicHelpers) state._useDynamicHelpers = true;
222
254
  let helperMap;
223
255
  if (shouldUseDynamicHelpers) helperMap = {
@@ -235,21 +267,22 @@ const intlayerOptimizeBabelPlugin = (babel) => {
235
267
  const originalImportedName = state._callerMap?.get(callee.name);
236
268
  if (!originalImportedName) return;
237
269
  state._hasValidImport = true;
238
- const arg = path.node.arguments[0];
239
- let key;
240
- if (arg && t.isStringLiteral(arg)) key = arg.value;
241
- else if (arg && t.isTemplateLiteral(arg) && arg.expressions.length === 0 && arg.quasis.length === 1) key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;
270
+ const key = getKeyFromArgument(path.node.arguments[0], t);
242
271
  if (!key) return;
272
+ const callerPackage = state._callerPackageMap?.get(callee.name);
243
273
  const importMode = state.opts.importMode;
244
274
  const isUseIntlayer = originalImportedName === "useIntlayer";
245
- const useDynamicHelpers = Boolean(state._useDynamicHelpers);
246
- let perCallMode = "static";
247
275
  const dictionaryOverrideMode = state.opts.dictionaryModeMap?.[key];
276
+ const effectiveImportMode = dictionaryOverrideMode ?? importMode;
277
+ const packageHasFetchCall = callerPackage !== void 0 && packagesWithFetchCall.has(callerPackage);
278
+ const usesStaticSsrDynamicFallback = isUseIntlayer && !packageHasFetchCall && shouldUseStaticSsrDynamicFallback(callerPackage, effectiveImportMode, state.opts);
279
+ const useDynamicHelpers = Boolean(state._useDynamicHelpers) && !usesStaticSsrDynamicFallback;
280
+ let perCallMode = "static";
248
281
  if (isUseIntlayer && useDynamicHelpers) {
249
282
  if (dictionaryOverrideMode) perCallMode = dictionaryOverrideMode;
250
283
  else if (importMode === "dynamic") perCallMode = "dynamic";
251
284
  else if (importMode === "fetch") perCallMode = "fetch";
252
- } else if (isUseIntlayer && !useDynamicHelpers) {
285
+ } else if (isUseIntlayer && !useDynamicHelpers && !usesStaticSsrDynamicFallback) {
253
286
  if (dictionaryOverrideMode === "dynamic" || dictionaryOverrideMode === "fetch") perCallMode = dictionaryOverrideMode;
254
287
  }
255
288
  let ident;