@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.
- package/dist/cjs/babel-plugin-intlayer-extract.cjs +2 -1
- package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +14 -9
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-minify.cjs +83 -0
- package/dist/cjs/babel-plugin-intlayer-minify.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +58 -24
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-purge.cjs +403 -0
- package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
- package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
- package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
- package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
- package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/constants.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/detectPackageName.cjs +1 -0
- package/dist/cjs/extractContent/utils/detectPackageName.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs +1 -0
- package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/generateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getComponentName.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getExistingIntlayerInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getOrGenerateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs +1 -0
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
- package/dist/cjs/extractScriptBlocks.cjs +1 -0
- package/dist/cjs/extractScriptBlocks.cjs.map +1 -1
- package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
- package/dist/cjs/getOptimizePluginOptions.cjs +1 -0
- package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
- package/dist/cjs/getPurgePluginOptions.cjs +108 -0
- package/dist/cjs/getPurgePluginOptions.cjs.map +1 -0
- package/dist/cjs/index.cjs +9 -1
- package/dist/cjs/transformers.cjs +1 -0
- package/dist/cjs/transformers.cjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs +2 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +14 -9
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-minify.mjs +82 -0
- package/dist/esm/babel-plugin-intlayer-minify.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +57 -24
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-purge.mjs +400 -0
- package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
- package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
- package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
- package/dist/esm/extractContent/extractContent.mjs.map +1 -1
- package/dist/esm/extractContent/processTsxFile.mjs.map +1 -1
- package/dist/esm/extractContent/utils/constants.mjs.map +1 -1
- package/dist/esm/extractContent/utils/detectPackageName.mjs.map +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs.map +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/generateKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getComponentName.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getExistingIntlayerInfo.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getOrGenerateKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/resolveDictionaryKey.mjs.map +1 -1
- package/dist/esm/extractContent/utils/shouldExtract.mjs.map +1 -1
- package/dist/esm/extractScriptBlocks.mjs.map +1 -1
- package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
- package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
- package/dist/esm/getPurgePluginOptions.mjs +106 -0
- package/dist/esm/getPurgePluginOptions.mjs.map +1 -0
- package/dist/esm/index.mjs +5 -2
- package/dist/esm/transformers.mjs.map +1 -1
- package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-minify.d.ts +84 -0
- package/dist/types/babel-plugin-intlayer-minify.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-optimize.d.ts +10 -5
- package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-purge.d.ts +127 -0
- package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
- package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
- package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
- package/dist/types/extractContent/extractContent.d.ts.map +1 -1
- package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
- package/dist/types/extractContent/utils/detectPackageName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/generateKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getComponentName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/shouldExtract.d.ts.map +1 -1
- package/dist/types/extractScriptBlocks.d.ts.map +1 -1
- package/dist/types/getExtractPluginOptions.d.ts.map +1 -1
- package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
- package/dist/types/getPurgePluginOptions.d.ts +83 -0
- package/dist/types/getPurgePluginOptions.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/transformers.d.ts.map +1 -1
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-optimize.cjs","names":[],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"sourcesContent":["import { dirname, join, relative } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { getPathHash } from '@intlayer/chokidar/utils';\nimport { normalizePath } from '@intlayer/config/utils';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'svelte-intlayer',\n 'vue-intlayer',\n 'angular-intlayer',\n 'preact-intlayer',\n 'solid-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n];\n\nconst CALLER_LIST = ['useIntlayer', 'getIntlayer'] as const;\n\n/**\n * Packages that support dynamic import\n */\nconst PACKAGE_LIST_DYNAMIC = [\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'preact-intlayer',\n 'vue-intlayer',\n 'solid-intlayer',\n 'svelte-intlayer',\n 'angular-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n] as const;\n\nconst STATIC_IMPORT_FUNCTION = {\n getIntlayer: 'getDictionary',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\n/**\n * Options for the optimization Babel plugin\n */\nexport type OptimizePluginOptions = {\n /**\n * If false, the plugin will not apply any transformation.\n */\n optimize?: boolean;\n /**\n * The path to the dictionaries directory.\n */\n dictionariesDir: string;\n /**\n * The path to the dictionaries entry file.\n */\n dictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries entry file.\n */\n unmergedDictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries directory.\n */\n unmergedDictionariesDir: string;\n /**\n * The path to the dictionaries directory.\n */\n dynamicDictionariesDir: string;\n /**\n * The path to the dynamic dictionaries entry file.\n */\n dynamicDictionariesEntryPath: string;\n /**\n * The path to the fetch dictionaries directory.\n */\n fetchDictionariesDir: string;\n /**\n * The path to the fetch dictionaries entry file.\n */\n fetchDictionariesEntryPath: string;\n /**\n * If true, the plugin will replace the dictionary entry file with `export default {}`.\n */\n replaceDictionaryEntry: boolean;\n /**\n * If true, the plugin will activate the dynamic import of the dictionaries. It will rely on Suspense to load the dictionaries.\n */\n importMode: 'static' | 'dynamic' | 'fetch' | undefined;\n /**\n * Map of dictionary keys to their specific import mode.\n */\n dictionaryModeMap?: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n /**\n * Files list to traverse.\n */\n filesList: string[];\n};\n\ntype State = PluginPass & {\n opts: OptimizePluginOptions;\n /** map key → generated ident (per-file) for static imports */\n _newStaticImports?: Map<string, BabelTypes.Identifier>;\n /** map key → generated ident (per-file) for dynamic imports */\n _newDynamicImports?: Map<string, BabelTypes.Identifier>;\n /** whether the current file imported *any* intlayer package */\n _hasValidImport?: boolean;\n /** map from local identifier name to the imported intlayer func name ('useIntlayer' | 'getIntlayer') */\n _callerMap?: Map<string, (typeof CALLER_LIST)[number]>;\n /** whether the current file *is* the dictionaries entry file */\n _isDictEntry?: boolean;\n /** whether dynamic helpers are active for this file */\n _useDynamicHelpers?: boolean;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n};\n\n/**\n * Replicates the xxHash64 → Base-62 algorithm used by the SWC version\n * and prefixes an underscore so the generated identifiers never collide\n * with user-defined ones.\n */\nconst makeIdent = (\n key: string,\n t: typeof BabelTypes\n): BabelTypes.Identifier => {\n const hash = getPathHash(key);\n return t.identifier(`_${hash}`);\n};\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch'\n): string => {\n let relativePath = join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n relativePath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n relativePath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n let rel = relative(dirname(fromFile), relativePath);\n\n // Fix windows path\n rel = normalizePath(rel);\n\n // Fix relative path\n if (!rel.startsWith('./') && !rel.startsWith('../')) {\n rel = `./${rel}`;\n }\n\n return rel;\n};\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()` and `getIntlayer()` from various Intlayer\n * packages into optimized dictionary access patterns, automatically importing the required\n * dictionary files based on the configured import mode.\n *\n * ## Supported Input Patterns\n *\n * The plugin recognizes these function calls:\n *\n * ```ts\n * // useIntlayer\n * import { useIntlayer } from 'react-intlayer';\n * import { useIntlayer } from 'next-intlayer';\n *\n * // getIntlayer\n * import { getIntlayer } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * ```\n *\n * ## Transformation Modes\n *\n * ### Static Mode (default: `importMode = \"static\"`)\n *\n * Imports JSON dictionaries directly and replaces function calls with dictionary access:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import { useDictionary as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash);\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Dynamic Mode (`importMode = \"dynamic\"`)\n *\n * Uses dynamic dictionary loading with Suspense support:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Live Mode (`importMode = \"live\"`)\n *\n * Uses live-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"live\" value:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_fetch, \"app\");\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * > If `dictionaryModeMap` does not include the key with \"live\" value, the plugin will fallback to the dynamic impor\n *\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n */\nexport const intlayerOptimizeBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-transform',\n\n pre() {\n this._newStaticImports = new Map();\n this._newDynamicImports = new Map();\n this._callerMap = new Map();\n this._isIncluded = true;\n this._hasValidImport = false;\n this._isDictEntry = false;\n this._useDynamicHelpers = false;\n\n // If optimize is false, skip processing entirely\n if (this.opts.optimize === false) {\n this._isIncluded = false;\n return;\n }\n\n // If filesList is provided, check if current file is included\n const filename = this.file.opts.filename;\n if (this.opts.filesList && filename) {\n const isIncluded = this.opts.filesList.includes(filename);\n\n if (!isIncluded) {\n // Force _isIncluded to false to skip processing\n this._isIncluded = false;\n return;\n }\n }\n },\n\n visitor: {\n /* If this file *is* the dictionaries entry, short-circuit: export {} */\n Program: {\n enter(programPath, state) {\n // Safe access to filename\n const filename = state.file.opts.filename;\n\n // Check if this is the correct file to transform\n\n if (\n state.opts.replaceDictionaryEntry &&\n filename === state.opts.dictionariesEntryPath\n ) {\n state._isDictEntry = true;\n\n // Traverse the program to surgically remove/edit specific parts\n programPath.traverse({\n // Remove all import statements (cleaning up 'sssss.json')\n ImportDeclaration(path) {\n path.remove();\n },\n\n // Find the variable definition and empty the object\n VariableDeclarator(path) {\n // We look for: const x = { ... }\n\n if (t.isObjectExpression(path.node.init)) {\n // Set the object properties to an empty array: {}\n path.node.init.properties = [];\n }\n },\n });\n\n // (Optional) Stop other plugins from processing this file further if needed\n // programPath.stop();\n }\n },\n\n /**\n * After full traversal, process imports and call expressions, then inject the JSON dictionary imports.\n *\n * We do the transformation in Program.exit (via a manual traverse) rather than using\n * top-level ImportDeclaration/CallExpression visitors. This ensures that if another plugin\n * (like babel-plugin-intlayer-extract) adds new useIntlayer calls in its Program.exit,\n * we will see and transform them here because our Program.exit runs after theirs.\n */\n exit(programPath, state) {\n if (state._isDictEntry) return; // nothing else to do – already replaced\n\n if (!state._isIncluded) return; // early-out if file is not included\n\n // Manual traversal to process imports and call expressions\n // This runs AFTER all other plugins' visitors have completed\n\n // Pre-pass to determine if we should use dynamic helpers\n let fileHasDynamicCall = false;\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (originalImportedName !== 'useIntlayer') return;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n fileHasDynamicCall = true;\n }\n },\n });\n\n programPath.traverse({\n /* Inspect every intlayer import */\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n // Mark that we do import from an intlayer package in this file\n state._hasValidImport = true;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (CALLER_LIST.includes(importedName as any)) {\n state._callerMap?.set(\n spec.local.name,\n importedName as (typeof CALLER_LIST)[number]\n );\n }\n\n const importMode = state.opts.importMode;\n // Determine whether this import should use the dynamic helpers.\n const shouldUseDynamicHelpers =\n (importMode === 'dynamic' ||\n importMode === 'fetch' ||\n fileHasDynamicCall) &&\n PACKAGE_LIST_DYNAMIC.includes(src as any);\n\n // Remember for later (CallExpression) whether we are using the dynamic helpers\n\n if (shouldUseDynamicHelpers) {\n state._useDynamicHelpers = true;\n }\n\n let helperMap: Record<string, string>;\n\n if (shouldUseDynamicHelpers) {\n // Use dynamic helpers for useIntlayer when dynamic mode is enabled\n helperMap = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n } as Record<string, string>;\n } else {\n // Use static helpers by default\n helperMap = STATIC_IMPORT_FUNCTION as Record<string, string>;\n }\n\n const newIdentifier = helperMap[importedName];\n\n // Only rewrite when we actually have a mapping for the imported\n // specifier (ignore unrelated named imports).\n\n if (newIdentifier) {\n // Keep the local alias intact (so calls remain `useIntlayer` /\n // `getIntlayer`), but rewrite the imported identifier so it\n // points to our helper implementation.\n spec.imported = t.identifier(newIdentifier);\n }\n }\n },\n\n /* Replace calls: useIntlayer(\"foo\") → useDictionary(_hash) or useDictionaryDynamic(_hash, \"foo\") */\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (!originalImportedName) return;\n\n // Ensure we ultimately emit helper imports for files that *invoke*\n // the hooks, even if they didn't import them directly (edge cases with\n // re-exports).\n state._hasValidImport = true;\n\n const arg = path.node.arguments[0];\n let key: string | undefined;\n if (arg && t.isStringLiteral(arg)) {\n key = arg.value;\n } else if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n key = arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n if (!key) return;\n\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const useDynamicHelpers = Boolean(state._useDynamicHelpers);\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'\n let perCallMode: 'static' | 'dynamic' | 'fetch' = 'static';\n\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (isUseIntlayer && useDynamicHelpers) {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic') {\n perCallMode = 'dynamic';\n } else if (importMode === 'fetch') {\n perCallMode = 'fetch';\n }\n } else if (isUseIntlayer && !useDynamicHelpers) {\n // If dynamic helpers are NOT active (global mode is static),\n // we STILL might want to force dynamic/live for this specific call\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n perCallMode = dictionaryOverrideMode;\n }\n }\n\n let ident: BabelTypes.Identifier;\n\n if (perCallMode === 'fetch') {\n // Use fetch dictionaries entry (live mode for selected keys)\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_fetch`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Helper: first argument is the dictionary entry, second is the key\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else if (perCallMode === 'dynamic') {\n // Use dynamic dictionaries entry\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n // Create a unique identifier for dynamic imports by appending a suffix\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_dyn`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Dynamic helper: first argument is the dictionary, second is the key.\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else {\n // Use static imports for getIntlayer or useIntlayer when not using dynamic helpers\n let staticIdent = state._newStaticImports?.get(key);\n\n if (!staticIdent) {\n staticIdent = makeIdent(key, t);\n state._newStaticImports?.set(key, staticIdent);\n }\n ident = staticIdent;\n\n // Static helper (useDictionary / getDictionary): replace key with ident.\n // After the splice above the key is always at index 0.\n path.node.arguments[0] = t.identifier(ident.name);\n }\n },\n });\n\n // Early-out if we touched nothing\n\n if (!state._hasValidImport) return;\n\n const file = state.file.opts.filename!;\n const dictionariesDir = state.opts.dictionariesDir;\n const dynamicDictionariesDir = state.opts.dynamicDictionariesDir;\n const fetchDictionariesDir = state.opts.fetchDictionariesDir;\n const imports: BabelTypes.ImportDeclaration[] = [];\n\n // Generate static JSON imports (getIntlayer always uses JSON dictionaries)\n for (const [key, ident] of state._newStaticImports!) {\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static'\n );\n\n const importDeclarationNode = t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n );\n\n // Add 'type: json' attribute for JSON files\n importDeclarationNode.attributes = [\n t.importAttribute(t.identifier('type'), t.stringLiteral('json')),\n ];\n\n imports.push(importDeclarationNode);\n }\n\n // Generate dynamic/fetch imports (for useIntlayer when using dynamic/live helpers)\n for (const [key, ident] of state._newDynamicImports!) {\n const modeForThisIdent: 'dynamic' | 'fetch' = ident.name.endsWith(\n '_fetch'\n )\n ? 'fetch'\n : 'dynamic';\n\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n modeForThisIdent\n );\n imports.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n )\n );\n }\n\n if (!imports.length) return;\n\n /* Keep \"use client\" / \"use server\" directives at the very top. */\n const bodyPaths = programPath.get(\n 'body'\n ) as NodePath<BabelTypes.Statement>[];\n let insertPos = 0;\n for (const stmtPath of bodyPaths) {\n const stmt = stmtPath.node;\n\n if (\n t.isExpressionStatement(stmt) &&\n t.isStringLiteral(stmt.expression) &&\n !stmt.expression.value.startsWith('import') &&\n !stmt.expression.value.startsWith('require')\n ) {\n insertPos += 1;\n } else {\n break;\n }\n }\n\n programPath.node.body.splice(insertPos, 0, ...imports);\n },\n },\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-optimize.cjs","names":["normalizePath"],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"sourcesContent":["import { dirname, join, relative } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { getPathHash } from '@intlayer/chokidar/utils';\nimport { normalizePath } from '@intlayer/config/utils';\n\nconst PACKAGE_LIST = [\n 'intlayer',\n '@intlayer/core',\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'svelte-intlayer',\n 'vue-intlayer',\n 'angular-intlayer',\n 'preact-intlayer',\n 'solid-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n];\n\nconst CALLER_LIST = ['useIntlayer', 'getIntlayer'] as const;\n\n/**\n * Packages that support dynamic import\n */\nconst PACKAGE_LIST_DYNAMIC = [\n 'react-intlayer',\n 'react-intlayer/client',\n 'react-intlayer/server',\n 'next-intlayer',\n 'next-intlayer/client',\n 'next-intlayer/server',\n 'preact-intlayer',\n 'vue-intlayer',\n 'solid-intlayer',\n 'svelte-intlayer',\n 'angular-intlayer',\n 'lit-intlayer',\n 'vanilla-intlayer',\n] as const;\n\nconst STATIC_IMPORT_FUNCTION = {\n getIntlayer: 'getDictionary',\n useIntlayer: 'useDictionary',\n} as const;\n\nconst DYNAMIC_IMPORT_FUNCTION = {\n useIntlayer: 'useDictionaryDynamic',\n} as const;\n\ntype CallerName = (typeof CALLER_LIST)[number];\ntype ImportMode = 'static' | 'dynamic' | 'fetch';\n\n/**\n * Packages whose SSR dynamic helper should render synchronously with a static\n * dictionary. Solid streaming SSR can hydrate static output reliably, while\n * its dynamic resource path either suspends during hydration or serializes the\n * full dictionary into HTML.\n */\nconst PACKAGE_SSR_DYNAMIC_STATIC_FALLBACK = new Set<string>(['solid-intlayer']);\n\n/**\n * Options for the optimization Babel plugin\n */\nexport type OptimizePluginOptions = {\n /**\n * If false, the plugin will not apply any transformation.\n */\n optimize?: boolean;\n /**\n * The path to the dictionaries directory.\n */\n dictionariesDir: string;\n /**\n * The path to the dictionaries entry file.\n */\n dictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries entry file.\n */\n unmergedDictionariesEntryPath: string;\n /**\n * The path to the unmerged dictionaries directory.\n */\n unmergedDictionariesDir: string;\n /**\n * The path to the dictionaries directory.\n */\n dynamicDictionariesDir: string;\n /**\n * The path to the dynamic dictionaries entry file.\n */\n dynamicDictionariesEntryPath: string;\n /**\n * The path to the fetch dictionaries directory.\n */\n fetchDictionariesDir: string;\n /**\n * The path to the fetch dictionaries entry file.\n */\n fetchDictionariesEntryPath: string;\n /**\n * If true, the plugin will replace the dictionary entry file with `export default {}`.\n */\n replaceDictionaryEntry: boolean;\n /**\n * If true, the plugin will activate the dynamic import of the dictionaries. It will rely on Suspense to load the dictionaries.\n */\n importMode: 'static' | 'dynamic' | 'fetch' | undefined;\n /**\n * Map of dictionary keys to their specific import mode.\n */\n dictionaryModeMap?: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n /**\n * Files list to traverse.\n */\n filesList: string[];\n /**\n * Whether the current transform is for an SSR bundle.\n */\n isServer?: boolean;\n};\n\ntype State = PluginPass & {\n opts: OptimizePluginOptions;\n /** map key → generated ident (per-file) for static imports */\n _newStaticImports?: Map<string, BabelTypes.Identifier>;\n /** map key → generated ident (per-file) for dynamic imports */\n _newDynamicImports?: Map<string, BabelTypes.Identifier>;\n /** whether the current file imported *any* intlayer package */\n _hasValidImport?: boolean;\n /** map from local identifier name to the imported intlayer func name ('useIntlayer' | 'getIntlayer') */\n _callerMap?: Map<string, (typeof CALLER_LIST)[number]>;\n /** map from local identifier name to the intlayer package it was imported from */\n _callerPackageMap?: Map<string, string>;\n /** whether the current file *is* the dictionaries entry file */\n _isDictEntry?: boolean;\n /** whether dynamic helpers are active for this file */\n _useDynamicHelpers?: boolean;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n};\n\n/**\n * Replicates the xxHash64 → Base-62 algorithm used by the SWC version\n * and prefixes an underscore so the generated identifiers never collide\n * with user-defined ones.\n */\nconst makeIdent = (\n key: string,\n t: typeof BabelTypes\n): BabelTypes.Identifier => {\n const hash = getPathHash(key);\n return t.identifier(`_${hash}`);\n};\n\nconst computeImport = (\n fromFile: string,\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n fetchDictionariesDir: string,\n key: string,\n importMode: 'static' | 'dynamic' | 'fetch'\n): string => {\n let relativePath = join(dictionariesDir, `${key}.json`);\n\n if (importMode === 'fetch') {\n relativePath = join(fetchDictionariesDir, `${key}.mjs`);\n }\n\n if (importMode === 'dynamic') {\n relativePath = join(dynamicDictionariesDir, `${key}.mjs`);\n }\n\n let rel = relative(dirname(fromFile), relativePath);\n\n // Fix windows path\n rel = normalizePath(rel);\n\n // Fix relative path\n if (!rel.startsWith('./') && !rel.startsWith('../')) {\n rel = `./${rel}`;\n }\n\n return rel;\n};\n\nconst getKeyFromArgument = (\n arg: BabelTypes.Node | null | undefined,\n t: typeof BabelTypes\n): string | undefined => {\n if (arg && t.isStringLiteral(arg)) {\n return arg.value;\n }\n\n if (\n arg &&\n t.isTemplateLiteral(arg) &&\n arg.expressions.length === 0 &&\n arg.quasis.length === 1\n ) {\n return arg.quasis[0]?.value.cooked ?? arg.quasis[0]?.value.raw;\n }\n\n return undefined;\n};\n\nconst isCallerName = (name: string): name is CallerName =>\n CALLER_LIST.includes(name as CallerName);\n\nconst isDynamicPackage = (\n packageName: string\n): packageName is (typeof PACKAGE_LIST_DYNAMIC)[number] =>\n PACKAGE_LIST_DYNAMIC.includes(\n packageName as (typeof PACKAGE_LIST_DYNAMIC)[number]\n );\n\nconst shouldUseStaticSsrDynamicFallback = (\n callerPackage: string | undefined,\n importMode: ImportMode | undefined,\n opts: OptimizePluginOptions\n): boolean =>\n opts.isServer === true &&\n importMode === 'dynamic' &&\n callerPackage !== undefined &&\n PACKAGE_SSR_DYNAMIC_STATIC_FALLBACK.has(callerPackage);\n\n/**\n * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.\n *\n * This plugin transforms calls to `useIntlayer()` and `getIntlayer()` from various Intlayer\n * packages into optimized dictionary access patterns, automatically importing the required\n * dictionary files based on the configured import mode.\n *\n * ## Supported Input Patterns\n *\n * The plugin recognizes these function calls:\n *\n * ```ts\n * // useIntlayer\n * import { useIntlayer } from 'react-intlayer';\n * import { useIntlayer } from 'next-intlayer';\n *\n * // getIntlayer\n * import { getIntlayer } from 'intlayer';\n *\n * // Usage\n * const content = useIntlayer('app');\n * const content = getIntlayer('app');\n * ```\n *\n * ## Transformation Modes\n *\n * ### Static Mode (default: `importMode = \"static\"`)\n *\n * Imports JSON dictionaries directly and replaces function calls with dictionary access:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import { useDictionary as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash);\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Dynamic Mode (`importMode = \"dynamic\"`)\n *\n * Uses dynamic dictionary loading with Suspense support:\n *\n * **Output:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * ### Fetch Mode (`importMode = \"fetch\"`)\n *\n * Uses fetch-based dictionary loading for remote dictionaries:\n *\n * **Output if `dictionaryModeMap` includes the key with \"fetch\" value:**\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_fetch, \"app\");\n * const content2 = getIntlayer(_dicHash);\n * ```\n *\n * > If `dictionaryModeMap` does not include the key with \"fetch\" value, the plugin will fallback to the dynamic import mode.\n *\n * ```ts\n * import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };\n * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';\n * import { useDictionaryDynamic as useIntlayer } from 'react-intlayer';\n * import { getDictionary as getIntlayer } from 'intlayer';\n *\n * const content1 = useIntlayer(_dicHash_dyn, 'app');\n * const content2 = getIntlayer(_dicHash);\n * ```\n */\nexport const intlayerOptimizeBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-transform',\n\n pre() {\n this._newStaticImports = new Map();\n this._newDynamicImports = new Map();\n this._callerMap = new Map();\n this._callerPackageMap = new Map();\n this._isIncluded = true;\n this._hasValidImport = false;\n this._isDictEntry = false;\n this._useDynamicHelpers = false;\n\n // If optimize is false, skip processing entirely\n if (this.opts.optimize === false) {\n this._isIncluded = false;\n return;\n }\n\n // If filesList is provided, check if current file is included\n const filename = this.file.opts.filename\n ? normalizePath(this.file.opts.filename)\n : undefined;\n if (this.opts.filesList && filename) {\n const filesList = this.opts.filesList.map(normalizePath);\n const isIncluded = filesList.includes(filename);\n\n if (!isIncluded) {\n // Force _isIncluded to false to skip processing\n this._isIncluded = false;\n return;\n }\n }\n },\n\n visitor: {\n /* If this file *is* the dictionaries entry, short-circuit: export {} */\n Program: {\n enter(programPath, state) {\n // Safe access to filename\n const filename = state.file.opts.filename\n ? normalizePath(state.file.opts.filename)\n : undefined;\n const dictionariesEntryPath = state.opts.dictionariesEntryPath\n ? normalizePath(state.opts.dictionariesEntryPath)\n : undefined;\n\n // Check if this is the correct file to transform\n\n if (\n state.opts.replaceDictionaryEntry &&\n filename === dictionariesEntryPath\n ) {\n state._isDictEntry = true;\n\n // Traverse the program to surgically remove/edit specific parts\n programPath.traverse({\n // Remove all import statements (cleaning up 'sssss.json')\n ImportDeclaration(path) {\n path.remove();\n },\n\n // Find the variable definition and empty the object\n VariableDeclarator(path) {\n // We look for: const x = { ... }\n\n if (t.isObjectExpression(path.node.init)) {\n // Set the object properties to an empty array: {}\n path.node.init.properties = [];\n }\n },\n });\n\n // (Optional) Stop other plugins from processing this file further if needed\n // programPath.stop();\n }\n },\n\n /**\n * After full traversal, process imports and call expressions, then inject the JSON dictionary imports.\n *\n * We do the transformation in Program.exit (via a manual traverse) rather than using\n * top-level ImportDeclaration/CallExpression visitors. This ensures that if another plugin\n * (like babel-plugin-intlayer-extract) adds new useIntlayer calls in its Program.exit,\n * we will see and transform them here because our Program.exit runs after theirs.\n */\n exit(programPath, state) {\n if (state._isDictEntry) return; // nothing else to do – already replaced\n\n if (!state._isIncluded) return; // early-out if file is not included\n\n // Manual traversal to process imports and call expressions\n // This runs AFTER all other plugins' visitors have completed\n programPath.traverse({\n /* Inspect every intlayer import before deciding helper rewrites. */\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n state._hasValidImport = true;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (isCallerName(importedName)) {\n state._callerMap?.set(spec.local.name, importedName);\n state._callerPackageMap?.set(spec.local.name, src);\n }\n }\n },\n });\n\n // Pre-pass to determine if dictionary-level overrides require the\n // dynamic helper in an otherwise static file.\n const packagesWithDynamicCall = new Set<string>();\n const packagesWithFetchCall = new Set<string>();\n programPath.traverse({\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (originalImportedName !== 'useIntlayer') return;\n\n const callerPackage = state._callerPackageMap?.get(callee.name);\n if (!callerPackage) return;\n\n const key = getKeyFromArgument(path.node.arguments[0], t);\n if (!key) return;\n\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n\n if (dictionaryOverrideMode === 'dynamic') {\n packagesWithDynamicCall.add(callerPackage);\n } else if (dictionaryOverrideMode === 'fetch') {\n packagesWithFetchCall.add(callerPackage);\n }\n },\n });\n\n programPath.traverse({\n ImportDeclaration(path) {\n const src = path.node.source.value;\n\n if (!PACKAGE_LIST.includes(src)) return;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (!isCallerName(importedName)) continue;\n\n const importMode = state.opts.importMode;\n // Determine whether this import should use the dynamic helpers.\n const packageHasDynamicCall = packagesWithDynamicCall.has(src);\n const packageHasFetchCall = packagesWithFetchCall.has(src);\n // A package import can be rewritten to only one helper. Fetch\n // overrides therefore keep the dynamic helper for every\n // useIntlayer call from that package in this file.\n const shouldUseStaticFallback =\n !packageHasFetchCall &&\n shouldUseStaticSsrDynamicFallback(\n src,\n importMode === 'dynamic' || packageHasDynamicCall\n ? 'dynamic'\n : importMode,\n state.opts\n );\n const shouldUseDynamicHelpers =\n isDynamicPackage(src) &&\n (importMode === 'fetch' ||\n packageHasFetchCall ||\n ((importMode === 'dynamic' || packageHasDynamicCall) &&\n !shouldUseStaticFallback));\n\n // Remember for later (CallExpression) whether we are using the dynamic helpers\n\n if (shouldUseDynamicHelpers) {\n state._useDynamicHelpers = true;\n }\n\n let helperMap: Record<string, string>;\n\n if (shouldUseDynamicHelpers) {\n // Use dynamic helpers for useIntlayer when dynamic mode is enabled\n helperMap = {\n ...STATIC_IMPORT_FUNCTION,\n ...DYNAMIC_IMPORT_FUNCTION,\n } as Record<string, string>;\n } else {\n // Use static helpers by default\n helperMap = STATIC_IMPORT_FUNCTION as Record<string, string>;\n }\n\n const newIdentifier = helperMap[importedName];\n\n if (newIdentifier) {\n // Keep the local alias intact (so calls remain `useIntlayer` /\n // `getIntlayer`), but rewrite the imported identifier so it\n // points to our helper implementation.\n spec.imported = t.identifier(newIdentifier);\n }\n }\n },\n\n /* Replace calls: useIntlayer(\"foo\") → useDictionary(_hash) or useDictionaryDynamic(_hash, \"foo\") */\n CallExpression(path) {\n const callee = path.node.callee;\n\n if (!t.isIdentifier(callee)) return;\n\n const originalImportedName = state._callerMap?.get(callee.name);\n if (!originalImportedName) return;\n\n // Ensure we ultimately emit helper imports for files that *invoke*\n // the hooks, even if they didn't import them directly (edge cases with\n // re-exports).\n state._hasValidImport = true;\n\n const key = getKeyFromArgument(path.node.arguments[0], t);\n if (!key) return;\n\n const callerPackage = state._callerPackageMap?.get(callee.name);\n const importMode = state.opts.importMode;\n const isUseIntlayer = originalImportedName === 'useIntlayer';\n const dictionaryOverrideMode =\n state.opts.dictionaryModeMap?.[key];\n const effectiveImportMode = dictionaryOverrideMode ?? importMode;\n const packageHasFetchCall =\n callerPackage !== undefined &&\n packagesWithFetchCall.has(callerPackage);\n const usesStaticSsrDynamicFallback =\n isUseIntlayer &&\n !packageHasFetchCall &&\n shouldUseStaticSsrDynamicFallback(\n callerPackage,\n effectiveImportMode,\n state.opts\n );\n const useDynamicHelpers =\n Boolean(state._useDynamicHelpers) &&\n !usesStaticSsrDynamicFallback;\n\n // Decide per-call mode: 'static' | 'dynamic' | 'fetch'\n let perCallMode: ImportMode = 'static';\n\n if (isUseIntlayer && useDynamicHelpers) {\n if (dictionaryOverrideMode) {\n perCallMode = dictionaryOverrideMode;\n } else if (importMode === 'dynamic') {\n perCallMode = 'dynamic';\n } else if (importMode === 'fetch') {\n perCallMode = 'fetch';\n }\n } else if (\n isUseIntlayer &&\n !useDynamicHelpers &&\n !usesStaticSsrDynamicFallback\n ) {\n // If dynamic helpers are NOT active (global mode is static),\n // we STILL might want to force dynamic/fetch for this specific call\n\n if (\n dictionaryOverrideMode === 'dynamic' ||\n dictionaryOverrideMode === 'fetch'\n ) {\n perCallMode = dictionaryOverrideMode;\n }\n }\n\n let ident: BabelTypes.Identifier;\n\n if (perCallMode === 'fetch') {\n // Use fetch dictionaries entry for selected keys\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_fetch`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Helper: first argument is the dictionary entry, second is the key\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else if (perCallMode === 'dynamic') {\n // Use dynamic dictionaries entry\n let dynamicIdent = state._newDynamicImports?.get(key);\n\n if (!dynamicIdent) {\n // Create a unique identifier for dynamic imports by appending a suffix\n const hash = getPathHash(key);\n dynamicIdent = t.identifier(`_${hash}_dyn`);\n state._newDynamicImports?.set(key, dynamicIdent);\n }\n ident = dynamicIdent;\n\n // Dynamic helper: first argument is the dictionary, second is the key.\n path.node.arguments = [\n t.identifier(ident.name),\n ...path.node.arguments,\n ];\n } else {\n // Use static imports for getIntlayer or useIntlayer when not using dynamic helpers\n let staticIdent = state._newStaticImports?.get(key);\n\n if (!staticIdent) {\n staticIdent = makeIdent(key, t);\n state._newStaticImports?.set(key, staticIdent);\n }\n ident = staticIdent;\n\n // Static helper (useDictionary / getDictionary): replace key with ident.\n // After the splice above the key is always at index 0.\n path.node.arguments[0] = t.identifier(ident.name);\n }\n },\n });\n\n // Early-out if we touched nothing\n\n if (!state._hasValidImport) return;\n\n const file = state.file.opts.filename!;\n const dictionariesDir = state.opts.dictionariesDir;\n const dynamicDictionariesDir = state.opts.dynamicDictionariesDir;\n const fetchDictionariesDir = state.opts.fetchDictionariesDir;\n const imports: BabelTypes.ImportDeclaration[] = [];\n\n // Generate static JSON imports (getIntlayer always uses JSON dictionaries)\n for (const [key, ident] of state._newStaticImports!) {\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n 'static'\n );\n\n const importDeclarationNode = t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n );\n\n // Add 'type: json' attribute for JSON files\n importDeclarationNode.attributes = [\n t.importAttribute(t.identifier('type'), t.stringLiteral('json')),\n ];\n\n imports.push(importDeclarationNode);\n }\n\n // Generate dynamic/fetch imports (for useIntlayer when using dynamic/fetch helpers)\n for (const [key, ident] of state._newDynamicImports!) {\n const modeForThisIdent: 'dynamic' | 'fetch' = ident.name.endsWith(\n '_fetch'\n )\n ? 'fetch'\n : 'dynamic';\n\n const rel = computeImport(\n file,\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n key,\n modeForThisIdent\n );\n imports.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(ident.name))],\n t.stringLiteral(rel)\n )\n );\n }\n\n if (!imports.length) return;\n\n /* Keep \"use client\" / \"use server\" directives at the very top. */\n const bodyPaths = programPath.get(\n 'body'\n ) as NodePath<BabelTypes.Statement>[];\n let insertPos = 0;\n for (const stmtPath of bodyPaths) {\n const stmt = stmtPath.node;\n\n if (\n t.isExpressionStatement(stmt) &&\n t.isStringLiteral(stmt.expression) &&\n !stmt.expression.value.startsWith('import') &&\n !stmt.expression.value.startsWith('require')\n ) {\n insertPos += 1;\n } else {\n break;\n }\n }\n\n programPath.node.body.splice(insertPos, 0, ...imports);\n },\n },\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_babel_plugin_intlayer_usage_analyzer = require('./babel-plugin-intlayer-usage-analyzer.cjs');
|
|
4
|
+
const require_extractScriptBlocks = require('./extractScriptBlocks.cjs');
|
|
5
|
+
const require_babel_plugin_intlayer_field_rename = require('./babel-plugin-intlayer-field-rename.cjs');
|
|
6
|
+
const require_transformers = require('./transformers.cjs');
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
let node_fs = require("node:fs");
|
|
9
|
+
let _babel_core = require("@babel/core");
|
|
10
|
+
|
|
11
|
+
//#region src/babel-plugin-intlayer-purge.ts
|
|
12
|
+
/**
|
|
13
|
+
* Cache of built {@link PruneContext} objects, keyed by the project's
|
|
14
|
+
* `baseDir`. Each context is built exactly once per Node.js process.
|
|
15
|
+
*/
|
|
16
|
+
const _pruneContextCache = /* @__PURE__ */ new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Tracks base directories whose full analysis + dictionary-write cycle has
|
|
19
|
+
* already completed, to avoid repeating work across file transforms.
|
|
20
|
+
*/
|
|
21
|
+
const _completedBaseDirs = /* @__PURE__ */ new Set();
|
|
22
|
+
/**
|
|
23
|
+
* Returns the shared {@link PruneContext} for the given base directory, or
|
|
24
|
+
* `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for
|
|
25
|
+
* that directory.
|
|
26
|
+
*
|
|
27
|
+
* Used by {@link intlayerMinifyBabelPlugin} to read the rename map without
|
|
28
|
+
* creating a circular dependency.
|
|
29
|
+
*/
|
|
30
|
+
const getSharedPruneContext = (baseDir) => _pruneContextCache.get(baseDir) ?? null;
|
|
31
|
+
const isTranslationNode = (value) => typeof value === "object" && value !== null && value.nodeType === "translation" && typeof value.translation === "object";
|
|
32
|
+
const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
/**
|
|
34
|
+
* Removes unused fields from a **static** dictionary (all locales in one
|
|
35
|
+
* file). Supports shape A (translation node at the root) and shape B (flat
|
|
36
|
+
* record of translation nodes per field).
|
|
37
|
+
*/
|
|
38
|
+
const pruneStaticDictionaryContent = (dictionary, usedFieldNames) => {
|
|
39
|
+
const { content } = dictionary;
|
|
40
|
+
if (isTranslationNode(content)) {
|
|
41
|
+
const firstLocaleValue = Object.values(content.translation)[0];
|
|
42
|
+
if (isPlainRecord(firstLocaleValue)) {
|
|
43
|
+
const prunedTranslation = {};
|
|
44
|
+
for (const [locale, localeContent] of Object.entries(content.translation)) {
|
|
45
|
+
if (!isPlainRecord(localeContent)) {
|
|
46
|
+
prunedTranslation[locale] = localeContent;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const prunedLocaleFields = {};
|
|
50
|
+
for (const [fieldName, fieldValue] of Object.entries(localeContent)) if (usedFieldNames.has(fieldName)) prunedLocaleFields[fieldName] = fieldValue;
|
|
51
|
+
prunedTranslation[locale] = prunedLocaleFields;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
prunedDictionary: {
|
|
55
|
+
...dictionary,
|
|
56
|
+
content: {
|
|
57
|
+
...content,
|
|
58
|
+
translation: prunedTranslation
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
wasRecognised: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (isPlainRecord(content) && !isTranslationNode(content)) {
|
|
66
|
+
const prunedContent = {};
|
|
67
|
+
for (const [fieldName, fieldValue] of Object.entries(content)) if (usedFieldNames.has(fieldName)) prunedContent[fieldName] = fieldValue;
|
|
68
|
+
return {
|
|
69
|
+
prunedDictionary: {
|
|
70
|
+
...dictionary,
|
|
71
|
+
content: prunedContent
|
|
72
|
+
},
|
|
73
|
+
wasRecognised: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
prunedDictionary: dictionary,
|
|
78
|
+
wasRecognised: false
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Removes unused fields from a **dynamic / per-locale** dictionary file
|
|
83
|
+
* (one JSON per locale, flat `content` record).
|
|
84
|
+
*/
|
|
85
|
+
const pruneDynamicDictionaryContent = (dictionary, usedFieldNames) => {
|
|
86
|
+
const { content } = dictionary;
|
|
87
|
+
if (!isPlainRecord(content)) return {
|
|
88
|
+
prunedDictionary: dictionary,
|
|
89
|
+
wasRecognised: false
|
|
90
|
+
};
|
|
91
|
+
const prunedContent = {};
|
|
92
|
+
for (const [fieldName, fieldValue] of Object.entries(content)) if (usedFieldNames.has(fieldName)) prunedContent[fieldName] = fieldValue;
|
|
93
|
+
return {
|
|
94
|
+
prunedDictionary: {
|
|
95
|
+
...dictionary,
|
|
96
|
+
content: prunedContent
|
|
97
|
+
},
|
|
98
|
+
wasRecognised: true
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Recursively renames user-defined content fields using `renameMap`.
|
|
103
|
+
* Translation nodes, arrays, and primitives follow the same traversal rules
|
|
104
|
+
* as in the Vite-based minify plugin.
|
|
105
|
+
*/
|
|
106
|
+
const renameContentRecursively = (value, renameMap) => {
|
|
107
|
+
if (Array.isArray(value)) return value.map((element) => renameContentRecursively(element, renameMap));
|
|
108
|
+
if (!value || typeof value !== "object") return value;
|
|
109
|
+
const record = value;
|
|
110
|
+
if (typeof record.nodeType === "string" && record.translation && typeof record.translation === "object" && !Array.isArray(record.translation)) {
|
|
111
|
+
const renamedTranslation = {};
|
|
112
|
+
for (const [locale, localeValue] of Object.entries(record.translation)) renamedTranslation[locale] = renameContentRecursively(localeValue, renameMap);
|
|
113
|
+
return {
|
|
114
|
+
...record,
|
|
115
|
+
translation: renamedTranslation
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const result = {};
|
|
119
|
+
for (const [key, val] of Object.entries(record)) {
|
|
120
|
+
const renameEntry = renameMap.get(key);
|
|
121
|
+
if (renameEntry) result[renameEntry.shortName] = renameContentRecursively(val, renameEntry.children);
|
|
122
|
+
else result[key] = val;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming
|
|
128
|
+
* only the keys inside `content` while leaving top-level metadata untouched.
|
|
129
|
+
*/
|
|
130
|
+
const applyFieldRenameToDict = (dict, renameMap) => {
|
|
131
|
+
const content = dict.content;
|
|
132
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) return dict;
|
|
133
|
+
return {
|
|
134
|
+
...dict,
|
|
135
|
+
content: renameContentRecursively(content, renameMap)
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Runs the usage-analyser Babel plugin synchronously on a single code block,
|
|
140
|
+
* accumulating results into `pruneContext`.
|
|
141
|
+
*/
|
|
142
|
+
const analyzeCodeBlockSync = (code, sourceFilePath, pruneContext) => {
|
|
143
|
+
try {
|
|
144
|
+
(0, _babel_core.transformSync)(code, {
|
|
145
|
+
filename: sourceFilePath,
|
|
146
|
+
plugins: [require_babel_plugin_intlayer_usage_analyzer.makeUsageAnalyzerBabelPlugin(pruneContext)],
|
|
147
|
+
parserOpts: require_transformers.BABEL_PARSER_OPTIONS,
|
|
148
|
+
ast: false,
|
|
149
|
+
code: false
|
|
150
|
+
});
|
|
151
|
+
} catch {
|
|
152
|
+
pruneContext.hasUnparsableSourceFiles = true;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Reads a source file from disk and runs the usage-analyser synchronously.
|
|
157
|
+
* SFC files (Vue / Svelte) are handled by extracting script blocks first.
|
|
158
|
+
*/
|
|
159
|
+
const analyzeSourceFileSync = (sourceFilePath, pruneContext) => {
|
|
160
|
+
let code;
|
|
161
|
+
try {
|
|
162
|
+
code = (0, node_fs.readFileSync)(sourceFilePath, "utf-8");
|
|
163
|
+
} catch {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!require_transformers.INTLAYER_OR_COMPAT_USAGE_REGEX.test(code)) return;
|
|
167
|
+
const scriptBlocks = require_extractScriptBlocks.extractScriptBlocks(sourceFilePath, code);
|
|
168
|
+
for (const block of scriptBlocks) {
|
|
169
|
+
if (!require_transformers.INTLAYER_OR_COMPAT_USAGE_REGEX.test(block.content)) continue;
|
|
170
|
+
analyzeCodeBlockSync(block.content, sourceFilePath, pruneContext);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Reads compiled dictionary JSON files to build the nested field-rename maps,
|
|
175
|
+
* mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's
|
|
176
|
+
* `buildStart` hook. Results are stored in
|
|
177
|
+
* `pruneContext.dictionaryKeyToFieldRenameMap`.
|
|
178
|
+
*/
|
|
179
|
+
const buildRenameMapsSynchronously = (dictionariesDir, dynamicDictionariesDir, dictionaryKeyToImportModeMap, pruneContext) => {
|
|
180
|
+
for (const [dictionaryKey, fieldUsage] of pruneContext.dictionaryKeyToFieldUsageMap) {
|
|
181
|
+
if (fieldUsage === "all") continue;
|
|
182
|
+
if (dictionaryKeyToImportModeMap[dictionaryKey] === "fetch") continue;
|
|
183
|
+
if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey)) continue;
|
|
184
|
+
let dictionaryContent = null;
|
|
185
|
+
const staticJsonPath = (0, node_path.join)(dictionariesDir, `${dictionaryKey}.json`);
|
|
186
|
+
if ((0, node_fs.existsSync)(staticJsonPath)) try {
|
|
187
|
+
const raw = (0, node_fs.readFileSync)(staticJsonPath, "utf-8");
|
|
188
|
+
dictionaryContent = JSON.parse(raw).content;
|
|
189
|
+
} catch {}
|
|
190
|
+
if (!dictionaryContent) {
|
|
191
|
+
const dynamicDirPath = (0, node_path.join)(dynamicDictionariesDir, dictionaryKey);
|
|
192
|
+
if ((0, node_fs.existsSync)(dynamicDirPath)) try {
|
|
193
|
+
const firstJsonFile = (0, node_fs.readdirSync)(dynamicDirPath).find((file) => file.endsWith(".json"));
|
|
194
|
+
if (firstJsonFile) {
|
|
195
|
+
const raw = (0, node_fs.readFileSync)((0, node_path.join)(dynamicDirPath, firstJsonFile), "utf-8");
|
|
196
|
+
dictionaryContent = JSON.parse(raw).content;
|
|
197
|
+
}
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
if (!dictionaryContent) continue;
|
|
201
|
+
const nestedRenameMap = require_babel_plugin_intlayer_field_rename.buildNestedRenameMapFromContent(dictionaryContent);
|
|
202
|
+
const opaqueFieldMap = pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey);
|
|
203
|
+
if (opaqueFieldMap) {
|
|
204
|
+
const dangerousEntries = [...opaqueFieldMap.entries()].filter(([fieldName]) => (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0);
|
|
205
|
+
for (const [fieldName] of dangerousEntries) {
|
|
206
|
+
const entry = nestedRenameMap.get(fieldName);
|
|
207
|
+
if (entry) entry.children = /* @__PURE__ */ new Map();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (nestedRenameMap.size > 0) pruneContext.dictionaryKeyToFieldRenameMap.set(dictionaryKey, nestedRenameMap);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const processStaticDictionaryFile = (filePath, pruneContext, shouldPurge, shouldMinify) => {
|
|
214
|
+
let rawJson;
|
|
215
|
+
try {
|
|
216
|
+
rawJson = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
217
|
+
} catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let parsedDict;
|
|
221
|
+
try {
|
|
222
|
+
parsedDict = JSON.parse(rawJson);
|
|
223
|
+
} catch {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const { key: dictionaryKey } = parsedDict;
|
|
227
|
+
if (!dictionaryKey) return;
|
|
228
|
+
if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;
|
|
229
|
+
let modified = false;
|
|
230
|
+
if (shouldPurge) {
|
|
231
|
+
const fieldUsage = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
|
|
232
|
+
if (fieldUsage && fieldUsage !== "all") {
|
|
233
|
+
const { prunedDictionary, wasRecognised } = pruneStaticDictionaryContent(parsedDict, fieldUsage);
|
|
234
|
+
if (!wasRecognised) {
|
|
235
|
+
pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
parsedDict = prunedDictionary;
|
|
239
|
+
modified = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (shouldMinify) {
|
|
243
|
+
const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
|
|
244
|
+
if (fieldRenameMap && fieldRenameMap.size > 0) {
|
|
245
|
+
parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);
|
|
246
|
+
modified = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!modified) return;
|
|
250
|
+
const outputDict = shouldMinify ? {
|
|
251
|
+
key: parsedDict.key,
|
|
252
|
+
content: parsedDict.content
|
|
253
|
+
} : parsedDict;
|
|
254
|
+
try {
|
|
255
|
+
(0, node_fs.writeFileSync)(filePath, JSON.stringify(outputDict), "utf-8");
|
|
256
|
+
} catch {}
|
|
257
|
+
};
|
|
258
|
+
const processDynamicDictionaryFile = (filePath, pruneContext, shouldPurge, shouldMinify) => {
|
|
259
|
+
let rawJson;
|
|
260
|
+
try {
|
|
261
|
+
rawJson = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
let parsedDict;
|
|
266
|
+
try {
|
|
267
|
+
parsedDict = JSON.parse(rawJson);
|
|
268
|
+
} catch {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const { key: dictionaryKey } = parsedDict;
|
|
272
|
+
if (!dictionaryKey) return;
|
|
273
|
+
if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;
|
|
274
|
+
let modified = false;
|
|
275
|
+
if (shouldPurge) {
|
|
276
|
+
const fieldUsage = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
|
|
277
|
+
if (fieldUsage && fieldUsage !== "all") {
|
|
278
|
+
const { prunedDictionary, wasRecognised } = pruneDynamicDictionaryContent(parsedDict, fieldUsage);
|
|
279
|
+
if (!wasRecognised) {
|
|
280
|
+
pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
parsedDict = prunedDictionary;
|
|
284
|
+
modified = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (shouldMinify) {
|
|
288
|
+
const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
|
|
289
|
+
if (fieldRenameMap && fieldRenameMap.size > 0) {
|
|
290
|
+
parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);
|
|
291
|
+
modified = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!modified) return;
|
|
295
|
+
const outputDict = shouldMinify ? {
|
|
296
|
+
key: parsedDict.key,
|
|
297
|
+
content: parsedDict.content,
|
|
298
|
+
locale: parsedDict.locale
|
|
299
|
+
} : parsedDict;
|
|
300
|
+
try {
|
|
301
|
+
(0, node_fs.writeFileSync)(filePath, JSON.stringify(outputDict), "utf-8");
|
|
302
|
+
} catch {}
|
|
303
|
+
};
|
|
304
|
+
const processAllDictionaryFiles = (dictionariesDir, dynamicDictionariesDir, pruneContext, shouldPurge, shouldMinify) => {
|
|
305
|
+
if ((0, node_fs.existsSync)(dictionariesDir)) for (const entry of (0, node_fs.readdirSync)(dictionariesDir)) {
|
|
306
|
+
if (!entry.endsWith(".json")) continue;
|
|
307
|
+
processStaticDictionaryFile((0, node_path.join)(dictionariesDir, entry), pruneContext, shouldPurge, shouldMinify);
|
|
308
|
+
}
|
|
309
|
+
if ((0, node_fs.existsSync)(dynamicDictionariesDir)) for (const keyDir of (0, node_fs.readdirSync)(dynamicDictionariesDir)) {
|
|
310
|
+
const keyDirPath = (0, node_path.join)(dynamicDictionariesDir, keyDir);
|
|
311
|
+
try {
|
|
312
|
+
for (const localeFile of (0, node_fs.readdirSync)(keyDirPath)) {
|
|
313
|
+
if (!localeFile.endsWith(".json")) continue;
|
|
314
|
+
processDynamicDictionaryFile((0, node_path.join)(keyDirPath, localeFile), pruneContext, shouldPurge, shouldMinify);
|
|
315
|
+
}
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Runs the full purge/minify pipeline for the given options, using a
|
|
321
|
+
* module-level cache so the work happens at most once per process per
|
|
322
|
+
* unique `baseDir`.
|
|
323
|
+
*/
|
|
324
|
+
const runPurgePipeline = (options) => {
|
|
325
|
+
const { baseDir, purge, minify, optimize, editorEnabled, dictionariesDir, dynamicDictionariesDir, componentFilesList, dictionaryKeyToImportModeMap } = options;
|
|
326
|
+
const cachedContext = _pruneContextCache.get(baseDir);
|
|
327
|
+
if (cachedContext) return cachedContext;
|
|
328
|
+
const pruneContext = require_babel_plugin_intlayer_usage_analyzer.createPruneContext();
|
|
329
|
+
_pruneContextCache.set(baseDir, pruneContext);
|
|
330
|
+
const shouldPurge = purge && !editorEnabled;
|
|
331
|
+
const shouldMinify = minify && !editorEnabled;
|
|
332
|
+
if (!shouldPurge && !shouldMinify || optimize === false) return pruneContext;
|
|
333
|
+
for (const sourceFilePath of componentFilesList) {
|
|
334
|
+
if (!require_transformers.SOURCE_FILE_REGEX.test(sourceFilePath)) continue;
|
|
335
|
+
analyzeSourceFileSync(sourceFilePath, pruneContext);
|
|
336
|
+
}
|
|
337
|
+
if (shouldMinify) buildRenameMapsSynchronously(dictionariesDir, dynamicDictionariesDir, dictionaryKeyToImportModeMap, pruneContext);
|
|
338
|
+
processAllDictionaryFiles(dictionariesDir, dynamicDictionariesDir, pruneContext, shouldPurge, shouldMinify);
|
|
339
|
+
return pruneContext;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Babel plugin that analyses all project source files and rewrites compiled
|
|
343
|
+
* dictionary JSON files in-place to remove unused content fields
|
|
344
|
+
* (`build.purge`) and/or rename them to short alphabetic aliases
|
|
345
|
+
* (`build.minify`).
|
|
346
|
+
*
|
|
347
|
+
* All option values must be pre-resolved via {@link getPurgePluginOptions}
|
|
348
|
+
* before being passed here — the plugin does not load the intlayer
|
|
349
|
+
* configuration itself.
|
|
350
|
+
*
|
|
351
|
+
* This plugin performs **file I/O as a side effect**: on the very first Babel
|
|
352
|
+
* transform in a given Node.js process it synchronously scans the component
|
|
353
|
+
* files listed in `options.componentFilesList`, builds field-usage data, and
|
|
354
|
+
* writes the processed dictionaries to disk. Subsequent transforms are
|
|
355
|
+
* no-ops.
|
|
356
|
+
*
|
|
357
|
+
* Source-code field renames (rewriting `content.title` → `content.a`) are
|
|
358
|
+
* handled by the companion {@link intlayerMinifyBabelPlugin}.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```js
|
|
362
|
+
* // babel.config.js
|
|
363
|
+
* const {
|
|
364
|
+
* intlayerPurgeBabelPlugin,
|
|
365
|
+
* intlayerMinifyBabelPlugin,
|
|
366
|
+
* intlayerOptimizeBabelPlugin,
|
|
367
|
+
* getPurgePluginOptions,
|
|
368
|
+
* getMinifyPluginOptions,
|
|
369
|
+
* getOptimizePluginOptions,
|
|
370
|
+
* } = require("@intlayer/babel");
|
|
371
|
+
*
|
|
372
|
+
* module.exports = {
|
|
373
|
+
* presets: ["next/babel"],
|
|
374
|
+
* plugins: [
|
|
375
|
+
* [intlayerPurgeBabelPlugin, getPurgePluginOptions()],
|
|
376
|
+
* [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],
|
|
377
|
+
* [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],
|
|
378
|
+
* ],
|
|
379
|
+
* };
|
|
380
|
+
* ```
|
|
381
|
+
*
|
|
382
|
+
* @remarks
|
|
383
|
+
* - Intended for **production builds** only. Dictionary JSON files are
|
|
384
|
+
* overwritten in-place; running `intlayer build` afterwards restores the
|
|
385
|
+
* originals.
|
|
386
|
+
* - The plugin is a no-op when `optimize` is `false` or `editorEnabled` is
|
|
387
|
+
* `true`.
|
|
388
|
+
*/
|
|
389
|
+
const intlayerPurgeBabelPlugin = (_babel) => ({
|
|
390
|
+
name: "intlayer-purge",
|
|
391
|
+
pre() {
|
|
392
|
+
const { baseDir } = this.opts;
|
|
393
|
+
if (_completedBaseDirs.has(baseDir)) return;
|
|
394
|
+
_completedBaseDirs.add(baseDir);
|
|
395
|
+
runPurgePipeline(this.opts);
|
|
396
|
+
},
|
|
397
|
+
visitor: {}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
exports.getSharedPruneContext = getSharedPruneContext;
|
|
402
|
+
exports.intlayerPurgeBabelPlugin = intlayerPurgeBabelPlugin;
|
|
403
|
+
//# sourceMappingURL=babel-plugin-intlayer-purge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-purge.cjs","names":["makeUsageAnalyzerBabelPlugin","BABEL_PARSER_OPTIONS","INTLAYER_OR_COMPAT_USAGE_REGEX","extractScriptBlocks","buildNestedRenameMapFromContent","createPruneContext","SOURCE_FILE_REGEX"],"sources":["../../src/babel-plugin-intlayer-purge.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { PluginObj, PluginPass } from '@babel/core';\nimport { transformSync } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { buildNestedRenameMapFromContent } from './babel-plugin-intlayer-field-rename';\nimport {\n createPruneContext,\n makeUsageAnalyzerBabelPlugin,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks } from './extractScriptBlocks';\nimport {\n BABEL_PARSER_OPTIONS,\n INTLAYER_OR_COMPAT_USAGE_REGEX,\n SOURCE_FILE_REGEX,\n} from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerPurgeBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getPurgePluginOptions}) so the plugin itself does not need to read\n * the configuration file on every file transform.\n */\nexport type PurgePluginOptions = {\n /**\n * Absolute path to the project root. Used as the cache key for the shared\n * {@link PruneContext} so two Babel transform pipelines for different\n * workspaces in the same process do not share state.\n */\n baseDir: string;\n\n /**\n * When `true`, remove unused content fields from compiled dictionary JSON\n * files. Mirrors `build.purge` in `intlayer.config.ts`.\n */\n purge: boolean;\n\n /**\n * When `true`, rename content fields to short alphabetic aliases\n * (`title` → `a`, etc.) and strip top-level metadata from compiled\n * dictionaries. Mirrors `build.minify` in `intlayer.config.ts`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. `undefined` means \"auto\" (active for\n * production builds). When explicitly `false`, the plugin is a no-op.\n * Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin skips all processing to preserve full dictionary\n * content for the visual editor. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n\n /**\n * Absolute path to the compiled static dictionaries directory\n * (`.intlayer/dictionaries/` by default).\n */\n dictionariesDir: string;\n\n /**\n * Absolute path to the compiled per-locale dynamic dictionaries directory\n * (`.intlayer/dynamic_dictionaries/` by default).\n */\n dynamicDictionariesDir: string;\n\n /**\n * Pre-built list of component source file paths to analyse for field-usage.\n * Populated by {@link getPurgePluginOptions} from the intlayer config's\n * `content` glob patterns.\n */\n componentFilesList: string[];\n\n /**\n * Per-dictionary import-mode overrides, keyed by dictionary `key`.\n * Dictionaries with mode `'fetch'` are excluded from field renaming because\n * their JSON is served from a remote API using the original field names.\n */\n dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n};\n\n// ── Shared module-level state ─────────────────────────────────────────────────\n\n/**\n * Cache of built {@link PruneContext} objects, keyed by the project's\n * `baseDir`. Each context is built exactly once per Node.js process.\n */\nconst _pruneContextCache = new Map<string, PruneContext>();\n\n/**\n * Tracks base directories whose full analysis + dictionary-write cycle has\n * already completed, to avoid repeating work across file transforms.\n */\nconst _completedBaseDirs = new Set<string>();\n\n/**\n * Returns the shared {@link PruneContext} for the given base directory, or\n * `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for\n * that directory.\n *\n * Used by {@link intlayerMinifyBabelPlugin} to read the rename map without\n * creating a circular dependency.\n */\nexport const getSharedPruneContext = (baseDir: string): PruneContext | null =>\n _pruneContextCache.get(baseDir) ?? null;\n\n// ── Dictionary JSON types ─────────────────────────────────────────────────────\n\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string;\n [extraKey: string]: unknown;\n};\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// ── Prune helpers (mirrors intlayerPrunePlugin) ───────────────────────────────\n\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/**\n * Removes unused fields from a **static** dictionary (all locales in one\n * file). Supports shape A (translation node at the root) and shape B (flat\n * record of translation nodes per field).\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A: { nodeType: \"translation\", translation: { en: { f1, f2 } } }\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n if (isPlainRecord(firstLocaleValue)) {\n const prunedTranslation: Record<string, unknown> = {};\n for (const [locale, localeContent] of Object.entries(\n content.translation\n )) {\n if (!isPlainRecord(localeContent)) {\n prunedTranslation[locale] = localeContent;\n continue;\n }\n const prunedLocaleFields: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(localeContent)) {\n if (usedFieldNames.has(fieldName)) {\n prunedLocaleFields[fieldName] = fieldValue;\n }\n }\n prunedTranslation[locale] = prunedLocaleFields;\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslation },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B: { field1: { nodeType: \"translation\", … }, field2: { … } }\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n const prunedContent: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContent[fieldName] = fieldValue;\n }\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContent as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Removes unused fields from a **dynamic / per-locale** dictionary file\n * (one JSON per locale, flat `content` record).\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n const prunedContent: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContent[fieldName] = fieldValue;\n }\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContent as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n};\n\n// ── Minify helpers (mirrors intlayerMinifyPlugin) ─────────────────────────────\n\n/**\n * Recursively renames user-defined content fields using `renameMap`.\n * Translation nodes, arrays, and primitives follow the same traversal rules\n * as in the Vite-based minify plugin.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale value with the same map.\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values.\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val;\n }\n }\n return result;\n};\n\n/**\n * Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming\n * only the keys inside `content` while leaving top-level metadata untouched.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// ── Synchronous source-file analysis ─────────────────────────────────────────\n\n/**\n * Runs the usage-analyser Babel plugin synchronously on a single code block,\n * accumulating results into `pruneContext`.\n */\nconst analyzeCodeBlockSync = (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): void => {\n try {\n transformSync(code, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false,\n });\n } catch {\n pruneContext.hasUnparsableSourceFiles = true;\n }\n};\n\n/**\n * Reads a source file from disk and runs the usage-analyser synchronously.\n * SFC files (Vue / Svelte) are handled by extracting script blocks first.\n */\nconst analyzeSourceFileSync = (\n sourceFilePath: string,\n pruneContext: PruneContext\n): void => {\n let code: string;\n try {\n code = readFileSync(sourceFilePath, 'utf-8');\n } catch {\n return;\n }\n\n if (!INTLAYER_OR_COMPAT_USAGE_REGEX.test(code)) return;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n for (const block of scriptBlocks) {\n if (!INTLAYER_OR_COMPAT_USAGE_REGEX.test(block.content)) continue;\n analyzeCodeBlockSync(block.content, sourceFilePath, pruneContext);\n }\n};\n\n// ── Build rename maps ─────────────────────────────────────────────────────────\n\n/**\n * Reads compiled dictionary JSON files to build the nested field-rename maps,\n * mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's\n * `buildStart` hook. Results are stored in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n */\nconst buildRenameMapsSynchronously = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n dictionaryKeyToImportModeMap: PurgePluginOptions['dictionaryKeyToImportModeMap'],\n pruneContext: PruneContext\n): void => {\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch') continue;\n if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey))\n continue;\n\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);\n if (existsSync(staticJsonPath)) {\n try {\n const raw = readFileSync(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n // Fall through to dynamic dict.\n }\n }\n\n if (!dictionaryContent) {\n const dynamicDirPath = join(dynamicDictionariesDir, dictionaryKey);\n if (existsSync(dynamicDirPath)) {\n try {\n const localeFiles = readdirSync(dynamicDirPath);\n const firstJsonFile = localeFiles.find((file) =>\n file.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = readFileSync(\n join(dynamicDirPath, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary not readable – skip rename for this key.\n }\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent);\n\n // Preserve children of opaque fields to avoid breaking child components.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey);\n if (opaqueFieldMap) {\n const dangerousEntries = [...opaqueFieldMap.entries()].filter(\n ([fieldName]) =>\n (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0\n );\n for (const [fieldName] of dangerousEntries) {\n const entry = nestedRenameMap.get(fieldName);\n if (entry) {\n entry.children = new Map();\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n};\n\n// ── Dictionary file writing ───────────────────────────────────────────────────\n\nconst processStaticDictionaryFile = (\n filePath: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean\n): void => {\n let rawJson: string;\n try {\n rawJson = readFileSync(filePath, 'utf-8');\n } catch {\n return;\n }\n\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(rawJson) as CompiledDictionaryJson;\n } catch {\n return;\n }\n\n const { key: dictionaryKey } = parsedDict;\n if (!dictionaryKey) return;\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const { prunedDictionary, wasRecognised } = pruneStaticDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n return;\n }\n parsedDict = prunedDictionary;\n modified = true;\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? { key: parsedDict.key, content: parsedDict.content }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processDynamicDictionaryFile = (\n filePath: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean\n): void => {\n let rawJson: string;\n try {\n rawJson = readFileSync(filePath, 'utf-8');\n } catch {\n return;\n }\n\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(rawJson) as CompiledDictionaryJson;\n } catch {\n return;\n }\n\n const { key: dictionaryKey } = parsedDict;\n if (!dictionaryKey) return;\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const { prunedDictionary, wasRecognised } = pruneDynamicDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n return;\n }\n parsedDict = prunedDictionary;\n modified = true;\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? {\n key: parsedDict.key,\n content: parsedDict.content,\n locale: parsedDict.locale,\n }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processAllDictionaryFiles = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean\n): void => {\n if (existsSync(dictionariesDir)) {\n for (const entry of readdirSync(dictionariesDir)) {\n if (!entry.endsWith('.json')) continue;\n processStaticDictionaryFile(\n join(dictionariesDir, entry),\n pruneContext,\n shouldPurge,\n shouldMinify\n );\n }\n }\n\n if (existsSync(dynamicDictionariesDir)) {\n for (const keyDir of readdirSync(dynamicDictionariesDir)) {\n const keyDirPath = join(dynamicDictionariesDir, keyDir);\n try {\n for (const localeFile of readdirSync(keyDirPath)) {\n if (!localeFile.endsWith('.json')) continue;\n processDynamicDictionaryFile(\n join(keyDirPath, localeFile),\n pruneContext,\n shouldPurge,\n shouldMinify\n );\n }\n } catch {\n // Unreadable key directory – skip.\n }\n }\n }\n};\n\n// ── Main initialisation ───────────────────────────────────────────────────────\n\n/**\n * Runs the full purge/minify pipeline for the given options, using a\n * module-level cache so the work happens at most once per process per\n * unique `baseDir`.\n */\nconst runPurgePipeline = (options: PurgePluginOptions): PruneContext => {\n const {\n baseDir,\n purge,\n minify,\n optimize,\n editorEnabled,\n dictionariesDir,\n dynamicDictionariesDir,\n componentFilesList,\n dictionaryKeyToImportModeMap,\n } = options;\n\n const cachedContext = _pruneContextCache.get(baseDir);\n if (cachedContext) return cachedContext;\n\n const pruneContext = createPruneContext();\n _pruneContextCache.set(baseDir, pruneContext);\n\n const shouldPurge = purge && !editorEnabled;\n const shouldMinify = minify && !editorEnabled;\n\n if ((!shouldPurge && !shouldMinify) || optimize === false)\n return pruneContext;\n\n // Phase 1: Synchronously analyse all component source files.\n for (const sourceFilePath of componentFilesList) {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) continue;\n analyzeSourceFileSync(sourceFilePath, pruneContext);\n }\n\n // Phase 2: Build field-rename maps (minify only).\n if (shouldMinify) {\n buildRenameMapsSynchronously(\n dictionariesDir,\n dynamicDictionariesDir,\n dictionaryKeyToImportModeMap,\n pruneContext\n );\n }\n\n // Phase 3: Write pruned / minified dictionary JSON files to disk.\n processAllDictionaryFiles(\n dictionariesDir,\n dynamicDictionariesDir,\n pruneContext,\n shouldPurge,\n shouldMinify\n );\n\n return pruneContext;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that analyses all project source files and rewrites compiled\n * dictionary JSON files in-place to remove unused content fields\n * (`build.purge`) and/or rename them to short alphabetic aliases\n * (`build.minify`).\n *\n * All option values must be pre-resolved via {@link getPurgePluginOptions}\n * before being passed here — the plugin does not load the intlayer\n * configuration itself.\n *\n * This plugin performs **file I/O as a side effect**: on the very first Babel\n * transform in a given Node.js process it synchronously scans the component\n * files listed in `options.componentFilesList`, builds field-usage data, and\n * writes the processed dictionaries to disk. Subsequent transforms are\n * no-ops.\n *\n * Source-code field renames (rewriting `content.title` → `content.a`) are\n * handled by the companion {@link intlayerMinifyBabelPlugin}.\n *\n * @example\n * ```js\n * // babel.config.js\n * const {\n * intlayerPurgeBabelPlugin,\n * intlayerMinifyBabelPlugin,\n * intlayerOptimizeBabelPlugin,\n * getPurgePluginOptions,\n * getMinifyPluginOptions,\n * getOptimizePluginOptions,\n * } = require(\"@intlayer/babel\");\n *\n * module.exports = {\n * presets: [\"next/babel\"],\n * plugins: [\n * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],\n * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],\n * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n * ],\n * };\n * ```\n *\n * @remarks\n * - Intended for **production builds** only. Dictionary JSON files are\n * overwritten in-place; running `intlayer build` afterwards restores the\n * originals.\n * - The plugin is a no-op when `optimize` is `false` or `editorEnabled` is\n * `true`.\n */\nexport const intlayerPurgeBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObj => ({\n name: 'intlayer-purge',\n\n pre(this: PluginPass & { opts: PurgePluginOptions }) {\n const { baseDir } = this.opts;\n\n if (_completedBaseDirs.has(baseDir)) return;\n _completedBaseDirs.add(baseDir);\n\n runPurgePipeline(this.opts);\n },\n\n visitor: {\n // No AST transforms: all work is done as a side effect in pre().\n },\n});\n"],"mappings":"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"}
|