vite-intlayer 9.5.11 → 9.5.12

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.
@@ -1,7 +1,7 @@
1
1
  import { colorize, getAppLogger } from "@intlayer/config/logger";
2
- import { formatPath, runOnce } from "@intlayer/engine/utils";
3
2
  import { join } from "node:path";
4
3
  import * as ANSIColors from "@intlayer/config/colors";
4
+ import { formatPath, runOnce } from "@intlayer/engine/utils";
5
5
 
6
6
  //#region src/intlayerMinifyPlugin.ts
7
7
  /**
@@ -2,11 +2,11 @@ import { intlayerVueAsyncPlugin } from "./intlayerVueAsyncPlugin.mjs";
2
2
  import { IMPORT_MODE } from "@intlayer/config/defaultValues";
3
3
  import { colorize, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
4
4
  import { normalizePath } from "@intlayer/config/utils";
5
- import { buildComponentFilesList, formatPath, runOnce } from "@intlayer/engine/utils";
6
5
  import { join } from "node:path";
7
6
  import * as ANSIColors from "@intlayer/config/colors";
7
+ import { buildComponentFilesList, formatPath, runOnce } from "@intlayer/engine/utils";
8
8
  import { readFile, readdir } from "node:fs/promises";
9
- import { INTLAYER_USAGE_REGEX, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, buildUsageCheckRegex, getNestedRenameEntryAtPath, optimizeSourceFile, preserveNestedDictionaryFields, renameFieldsInSourceFile } from "@intlayer/babel";
9
+ import { INTLAYER_USAGE_REGEX, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, buildUsageCheckRegex, getNestedRenameEntryAtPath, optimizeSourceFile, preserveNestedDictionaryFields } from "@intlayer/babel";
10
10
  import { requiresDictionaryRegistry } from "@intlayer/config/callers";
11
11
  import { getNestedDictionaryGraph, getNestedDictionaryReferences } from "@intlayer/core/dictionaryManipulator";
12
12
  import { getDictionaries } from "@intlayer/dictionaries-entry";
@@ -270,12 +270,8 @@ const intlayerOptimize = async (intlayerConfig, pruneContext, compatCallers) =>
270
270
  const isDictionaryEntryFile = sourceFilePath === dictionariesEntryPath;
271
271
  const isUsingIntlayer = (compatCallers?.length ? buildUsageCheckRegex(compatCallers.map((caller) => caller.callerName)) : INTLAYER_USAGE_REGEX).test(sourceCode);
272
272
  if (!isUsingIntlayer && !isDictionaryEntryFile) return null;
273
- let codeToOptimize = sourceCode;
274
- if (isFieldRenameEnabled && pruneContext && isUsingIntlayer) {
275
- const renamedCode = await renameFieldsInSourceFile(sourceFilePath, sourceCode, pruneContext);
276
- if (renamedCode) codeToOptimize = renamedCode;
277
- }
278
- const transformResult = await optimizeSourceFile(codeToOptimize, sourceFilePath, {
273
+ const fieldRenamePruneContext = isFieldRenameEnabled && pruneContext && isUsingIntlayer ? pruneContext : void 0;
274
+ const transformResult = await optimizeSourceFile(sourceCode, sourceFilePath, {
279
275
  optimize,
280
276
  dictionariesDir,
281
277
  dictionariesEntryPath,
@@ -292,7 +288,7 @@ const intlayerOptimize = async (intlayerConfig, pruneContext, compatCallers) =>
292
288
  dictionaryModeMap: dictionaryKeyToImportModeMap,
293
289
  isServer: options?.ssr === true,
294
290
  compatCallers
295
- });
291
+ }, fieldRenamePruneContext);
296
292
  if (!transformResult) return null;
297
293
  return {
298
294
  code: transformResult.code,
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n analyzeFieldUsageInFile,\n buildNestedRenameMapFromContent,\n buildUsageCheckRegex,\n type CallerDescriptor,\n getNestedRenameEntryAtPath,\n INTLAYER_USAGE_REGEX,\n optimizeSourceFile,\n type PruneContext,\n preserveNestedDictionaryFields,\n renameFieldsInSourceFile,\n SOURCE_FILE_REGEX,\n} from '@intlayer/babel';\nimport { requiresDictionaryRegistry } from '@intlayer/config/callers';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport {\n getNestedDictionaryGraph,\n getNestedDictionaryReferences,\n} from '@intlayer/core/dictionaryManipulator';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport {\n buildComponentFilesList,\n formatPath,\n runOnce,\n} from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport type { PluginOption } from 'vite';\nimport { intlayerVueAsyncPlugin } from './intlayerVueAsyncPlugin';\n\n// Plugin\n\n/**\n * Returns the Vite plugins responsible for the build optimisation step.\n *\n * Contains three internal plugins:\n *\n * 1. Vue async plugin – handles Vue SFC async script blocks.\n * 2. Usage analyser (`vite-intlayer-usage-analyzer`) – pre-scans every\n * component source file during `buildStart` to build the field-usage map\n * in `pruneContext`. This runs before any `transform` calls so the\n * downstream prune plugin always has complete data.\n * 3. Babel transform (`vite-intlayer-babel-transform`) – rewrites\n * `useIntlayer('key')` / `getIntlayer('key')` calls into\n * `useDictionary(_hash)` / `getDictionary(_hash)` and injects the\n * corresponding JSON (or dynamic `.mjs`) imports. Also applies field-name\n * renaming when `build.minify` is enabled.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Shared mutable state written here and read by the\n * prune and minify plugins. Pass `null` to skip\n * analysis (e.g. when both `purge` and `minify` are\n * disabled).\n */\nexport const intlayerOptimize = async (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null,\n compatCallers?: CallerDescriptor[]\n): Promise<PluginOption[]> => {\n try {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, purge, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const importMode =\n intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n unmergedDictionariesDir,\n fetchDictionariesDir,\n mainDir,\n baseDir,\n } = intlayerConfig.system;\n\n const dictionariesEntryPath = normalizePath(\n join(mainDir, 'dictionaries.mjs')\n );\n const unmergedDictionariesEntryPath = normalizePath(\n join(mainDir, 'unmerged_dictionaries.mjs')\n );\n const dynamicDictionariesEntryPath = normalizePath(\n join(mainDir, 'dynamic_dictionaries.mjs')\n );\n\n const componentFilesList =\n buildComponentFilesList(intlayerConfig).map(normalizePath);\n\n const transformableFilesList = [\n ...componentFilesList,\n dictionariesEntryPath,\n ];\n\n const dictionaries = getDictionaries(intlayerConfig);\n\n const dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch'\n > = {};\n (Object.values(dictionaries) as Dictionary[]).forEach((dictionary) => {\n dictionaryKeyToImportModeMap[dictionary.key] =\n dictionary.importMode ?? importMode ?? IMPORT_MODE;\n });\n\n // Dictionaries pulled in by `nest()`: resolved at runtime by `getNesting`\n // through the dictionary registry, never by a `useIntlayer` call site.\n const nestedDictionaryReferences = getNestedDictionaryReferences(\n Object.values(dictionaries)\n );\n\n // Dictionaries holding `nest()` references: their static import is\n // re-pointed at the companion module carrying the nest targets.\n const nestingDictionaryKeys = [\n ...getNestedDictionaryGraph(Object.values(dictionaries)).keys(),\n ];\n\n const isBuildOptimizeEnabled = (\n _config: unknown,\n env: { command: string }\n ) => {\n const isBuildCommand = env.command === 'build';\n return (optimize === undefined && isBuildCommand) || optimize === true;\n };\n\n // Field renaming rewrites the content keys the interpreter walks, so the\n // `keyPath` reported by `ContentSelector` would no longer resolve against\n // the unmerged dictionaries the visual editor edits. Purging and the\n // keyPath-neutral part of minification stay active while the editor is on.\n const isFieldRenameEnabled = !editorEnabled && !!minify;\n\n const isAnalysisEnabled = (_config: unknown, env: { command: string }) =>\n (!!purge || !!minify) && isBuildOptimizeEnabled(_config, env);\n\n // Compat callers the optimize pass cannot bind (`$_('home.title')`, a\n // bare `useI18n()`) still read the runtime registry: emptying it would\n // leave those call sites with no dictionary at all.\n const keepsDictionaryRegistry = requiresDictionaryRegistry(\n compatCallers ?? []\n );\n\n let partiallyMinifiedDictionariesCount = 0;\n\n return [\n intlayerVueAsyncPlugin(intlayerConfig, transformableFilesList),\n\n // Plugin 1: Usage analyser\n {\n name: 'vite-intlayer-usage-analyzer',\n enforce: 'pre',\n apply: isAnalysisEnabled,\n\n buildStart: async () => {\n if (!pruneContext) return;\n\n const extraCallerNames = (compatCallers ?? []).map(\n (caller) => caller.callerName\n );\n const usageCheckRegex = buildUsageCheckRegex(extraCallerNames);\n\n // Phase 1: Babel-based field-usage analysis for all component files\n await Promise.all(\n componentFilesList.map(async (sourceFilePath) => {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return;\n\n let sourceCode: string;\n try {\n sourceCode = await readFile(sourceFilePath, 'utf-8');\n } catch {\n return; // unreadable file – skip silently\n }\n\n if (!usageCheckRegex.test(sourceCode)) return;\n\n // For Vue/Svelte SFCs, the usage analyzer expects the raw script\n // content. `analyzeFieldUsageInFile` handles block extraction\n // internally via `extractScriptBlocks`.\n try {\n await analyzeFieldUsageInFile(\n sourceFilePath,\n sourceCode,\n pruneContext,\n compatCallers\n );\n } catch (parseError) {\n pruneContext.hasUnparsableSourceFiles = true;\n logger(\n [\n `Could not parse`,\n formatPath(sourceFilePath),\n `for field-usage analysis.`,\n 'Dictionaries whose usage cannot be confirmed will not be pruned.',\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n }\n })\n );\n\n // Phase 2: Framework-specific analysis for Vue / Svelte / Astro SFC\n // bindings that Babel scope analysis cannot resolve:\n // Vue → `.value` ref-accessor indirection\n // Svelte → `$` reactive store prefix\n // Astro → frontmatter variables referenced in the HTML template\n if (pruneContext.pendingFrameworkAnalysis.size > 0) {\n const vuePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n const sveltePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n const astroPending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n\n for (const [\n filePath,\n entries,\n ] of pruneContext.pendingFrameworkAnalysis) {\n if (filePath.endsWith('.vue')) {\n vuePending.set(filePath, entries);\n } else if (filePath.endsWith('.svelte')) {\n sveltePending.set(filePath, entries);\n } else if (filePath.endsWith('.astro')) {\n astroPending.set(filePath, entries);\n }\n }\n\n /** Merge framework-extracted field usage into pruneContext. */\n const mergeFrameworkResult = (\n dictionaryKey: string,\n fields: Set<string> | undefined\n ): void => {\n if (fields && fields.size > 0) {\n // The Babel rename plugin cannot update source-code property\n // accesses for SFC indirect patterns → suppress field renaming.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n const existing =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (existing === 'all') return;\n\n const merged =\n existing instanceof Set\n ? new Set([...existing, ...fields])\n : new Set(fields);\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n merged\n );\n } else {\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n 'all'\n );\n }\n };\n\n // Vue files\n if (vuePending.size > 0) {\n let extractVueIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const vueCompiler = await import('@intlayer/vue-compiler');\n extractVueIntlayerFieldUsage =\n vueCompiler.extractVueIntlayerFieldUsage;\n } catch {\n // @intlayer/vue-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of vuePending) {\n if (!extractVueIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractVueIntlayerFieldUsage(fileCode, entries);\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n\n // Svelte files\n if (sveltePending.size > 0) {\n let extractSvelteIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const svelteCompiler = await import(\n '@intlayer/svelte-compiler'\n );\n extractSvelteIntlayerFieldUsage =\n svelteCompiler.extractSvelteIntlayerFieldUsage;\n } catch {\n // @intlayer/svelte-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of sveltePending) {\n if (!extractSvelteIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractSvelteIntlayerFieldUsage(\n fileCode,\n entries\n );\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n\n // Astro files\n // Frontmatter variables are used in the HTML template, which is not\n // visible to Babel's scope analysis. Scan the template section for\n // `variableName.fieldName` accesses using a lightweight regex pass.\n if (astroPending.size > 0) {\n for (const [filePath, entries] of astroPending) {\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n // Extract only the template (everything after the closing ---).\n // The frontmatter was already handled by Babel in Phase 1.\n const fenceMatch = /^---\\r?\\n[\\s\\S]*?\\r?\\n---/.exec(fileCode);\n const template = fenceMatch\n ? fileCode.slice(fenceMatch.index + fenceMatch[0].length)\n : fileCode;\n\n for (const { variableName, dictionaryKey } of entries) {\n const escapedVar = variableName.replace(\n /[.*+?^${}()|[\\]\\\\]/g,\n '\\\\$&'\n );\n const fieldRe = new RegExp(\n `\\\\b${escapedVar}\\\\.([a-zA-Z_$][a-zA-Z0-9_$]*)`,\n 'g'\n );\n const foundFields = new Set<string>();\n let m = fieldRe.exec(template);\n while (m !== null) {\n foundFields.add(m[1]!);\n m = fieldRe.exec(template);\n }\n mergeFrameworkResult(\n dictionaryKey,\n foundFields.size > 0 ? foundFields : undefined\n );\n }\n }\n }\n }\n\n // Phase 2.5: Keep `nest()` targets resolvable — must run before the\n // rename maps of Phase 4 are built.\n preserveNestedDictionaryFields(\n pruneContext,\n nestedDictionaryReferences\n );\n\n // Phase 3: Warn about untracked bindings (plain variable assignments)\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n\n // Phase 4: Build field-rename map for minification\n // Reads each compiled dictionary JSON to discover the full nested\n // user-defined field structure, then builds a NestedRenameMap that\n // assigns short alphabetic aliases at every level.\n if (isFieldRenameEnabled) {\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n\n // Fetch-mode dictionaries are served from a remote API using\n // original field names – renaming would break the client/server\n // contract.\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch')\n continue;\n\n // SFC indirect access: skip field rename for these dictionaries\n // to avoid a JSON ↔ source mismatch at runtime.\n if (\n pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey)\n )\n continue;\n\n // Read dictionary content (static JSON first, then dynamic per-locale)\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(\n dictionariesDir,\n `${dictionaryKey}.json`\n );\n try {\n const raw = await readFile(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n try {\n const dynamicDir = join(\n dynamicDictionariesDir,\n dictionaryKey\n );\n const localeFiles = await readdir(dynamicDir);\n const firstJsonFile = localeFiles.find((f) =>\n f.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = await readFile(\n join(dynamicDir, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary file not readable – skip rename for this key\n }\n }\n\n if (!dictionaryContent) continue;\n\n // Build the rename map from ALL user-defined fields in the\n // dictionary — not just the ones statically consumed by source\n // files. Using the full set ensures that:\n // 1. Every field in the compiled JSON is renamed (even if\n // pruned-out fields still appear when purge is disabled).\n // 2. The short-name assignment is stable: the alphabetical\n // order of all fields determines each short name, so adding\n // or removing a consumer never changes names for others.\n // 3. There is no source ↔ JSON mismatch: both sides use the\n // identical map regardless of which subset is consumed.\n const nestedRenameMap =\n buildNestedRenameMapFromContent(dictionaryContent);\n\n // Skip renaming below opaque values that have nested\n // user-defined structure – renaming those sub-keys would\n // silently break child components that consume the value as-is.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n\n if (opaqueFieldMap) {\n const dangerousOccurrences = [\n ...opaqueFieldMap.values(),\n ].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n )?.children.size ?? 0) > 0\n );\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(\n `'${occurrence.fieldPath.join('.')}'`,\n ANSIColors.BLUE\n ),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (loc) => `\\n at ${formatPath(loc)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n // Disable renaming for the children of opaque values to\n // prevent breaking components that receive the value as a\n // prop.\n for (const occurrence of dangerousOccurrences) {\n const entry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (entry) {\n entry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-partial-minify-summary.lock'\n ),\n () => {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n },\n { cacheTimeoutMs: 1000 * 5 }\n );\n }\n }\n },\n },\n\n // Plugin 2: Babel transform\n {\n name: 'vite-intlayer-babel-transform',\n enforce: 'post', // Run after framework transformations (e.g. Vue SFC)\n apply: (_config, env) => {\n const isBuildCommand = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isBuildCommand || !isEnabled) return false;\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-optimize-plugin-enabled.lock'\n ),\n () => {\n logger([\n `Build optimization ${colorize('enabled', ANSIColors.GREEN)}`,\n colorize('(import mode:', ANSIColors.GREY_DARK),\n colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),\n colorize(')', ANSIColors.GREY_DARK),\n ]);\n\n // The minify plugin reports the field-rename step standing down\n // when the editor is enabled, so this line stays a plain one.\n if (minify) {\n logger(\n `Dictionary minification ${colorize('enabled', ANSIColors.GREEN)}`\n );\n }\n\n if (purge) {\n logger(\n `Dictionary purge unused keys ${colorize('enabled', ANSIColors.GREEN)}`\n );\n }\n },\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n },\n\n transform: async (sourceCode, moduleId, options) => {\n // Strip query parameters added by Vue/Svelte loaders\n // e.g. \"HelloWorld.vue?vue&type=script&setup=true&lang.ts\" → \"HelloWorld.vue\"\n const sourceFilePath = normalizePath(\n moduleId.split('?', 1)[0] ?? moduleId\n );\n\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;\n if (!transformableFilesList.includes(sourceFilePath)) return null;\n\n const isDictionaryEntryFile =\n sourceFilePath === dictionariesEntryPath;\n\n // Match native intlayer calls plus any configured compat callers\n // (`useTranslation`, `useTranslations`, …) so compat-only files are\n // still optimized.\n const optimizeUsageRegex = compatCallers?.length\n ? buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n )\n : INTLAYER_USAGE_REGEX;\n\n const isUsingIntlayer = optimizeUsageRegex.test(sourceCode);\n if (!isUsingIntlayer && !isDictionaryEntryFile) return null;\n\n // Step 1: Field rename (must run before the optimize pass, which\n // replaces useIntlayer → useDictionary and erases the dictionary key)\n let codeToOptimize = sourceCode;\n\n if (isFieldRenameEnabled && pruneContext && isUsingIntlayer) {\n const renamedCode = await renameFieldsInSourceFile(\n sourceFilePath,\n sourceCode,\n pruneContext\n );\n if (renamedCode) {\n codeToOptimize = renamedCode;\n }\n }\n\n // Step 2: Optimize (useIntlayer('key') → useDictionary(_hash))\n const transformResult = await optimizeSourceFile(\n codeToOptimize,\n sourceFilePath,\n {\n optimize,\n dictionariesDir,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n unmergedDictionariesDir,\n dynamicDictionariesDir,\n dynamicDictionariesEntryPath,\n fetchDictionariesDir,\n fetchDictionariesEntryPath: join(\n mainDir,\n 'fetch_dictionaries.mjs'\n ),\n importMode,\n filesList: transformableFilesList,\n replaceDictionaryEntry:\n isBuildOptimizeEnabled(null, { command: 'build' }) &&\n !keepsDictionaryRegistry,\n nestingDictionaryKeys,\n dictionaryModeMap: dictionaryKeyToImportModeMap,\n isServer: options?.ssr === true,\n compatCallers,\n }\n );\n\n if (!transformResult) return null;\n\n return {\n code: transformResult.code,\n map: transformResult.map as any,\n };\n },\n },\n ];\n } catch (pluginInitError) {\n console.warn(\n '[vite-intlayer] Failed to initialise optimization plugin:',\n pluginInitError\n );\n return [];\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n analyzeFieldUsageInFile,\n buildNestedRenameMapFromContent,\n buildUsageCheckRegex,\n type CallerDescriptor,\n getNestedRenameEntryAtPath,\n INTLAYER_USAGE_REGEX,\n optimizeSourceFile,\n type PruneContext,\n preserveNestedDictionaryFields,\n SOURCE_FILE_REGEX,\n} from '@intlayer/babel';\nimport { requiresDictionaryRegistry } from '@intlayer/config/callers';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport {\n getNestedDictionaryGraph,\n getNestedDictionaryReferences,\n} from '@intlayer/core/dictionaryManipulator';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport {\n buildComponentFilesList,\n formatPath,\n runOnce,\n} from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport type { PluginOption } from 'vite';\nimport { intlayerVueAsyncPlugin } from './intlayerVueAsyncPlugin';\n\n// Plugin\n\n/**\n * Returns the Vite plugins responsible for the build optimisation step.\n *\n * Contains three internal plugins:\n *\n * 1. Vue async plugin – handles Vue SFC async script blocks.\n * 2. Usage analyser (`vite-intlayer-usage-analyzer`) – pre-scans every\n * component source file during `buildStart` to build the field-usage map\n * in `pruneContext`. This runs before any `transform` calls so the\n * downstream prune plugin always has complete data.\n * 3. Babel transform (`vite-intlayer-babel-transform`) – rewrites\n * `useIntlayer('key')` / `getIntlayer('key')` calls into\n * `useDictionary(_hash)` / `getDictionary(_hash)` and injects the\n * corresponding JSON (or dynamic `.mjs`) imports. Also applies field-name\n * renaming when `build.minify` is enabled.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Shared mutable state written here and read by the\n * prune and minify plugins. Pass `null` to skip\n * analysis (e.g. when both `purge` and `minify` are\n * disabled).\n */\nexport const intlayerOptimize = async (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null,\n compatCallers?: CallerDescriptor[]\n): Promise<PluginOption[]> => {\n try {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, purge, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const importMode =\n intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n unmergedDictionariesDir,\n fetchDictionariesDir,\n mainDir,\n baseDir,\n } = intlayerConfig.system;\n\n const dictionariesEntryPath = normalizePath(\n join(mainDir, 'dictionaries.mjs')\n );\n const unmergedDictionariesEntryPath = normalizePath(\n join(mainDir, 'unmerged_dictionaries.mjs')\n );\n const dynamicDictionariesEntryPath = normalizePath(\n join(mainDir, 'dynamic_dictionaries.mjs')\n );\n\n const componentFilesList =\n buildComponentFilesList(intlayerConfig).map(normalizePath);\n\n const transformableFilesList = [\n ...componentFilesList,\n dictionariesEntryPath,\n ];\n\n const dictionaries = getDictionaries(intlayerConfig);\n\n const dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch'\n > = {};\n (Object.values(dictionaries) as Dictionary[]).forEach((dictionary) => {\n dictionaryKeyToImportModeMap[dictionary.key] =\n dictionary.importMode ?? importMode ?? IMPORT_MODE;\n });\n\n // Dictionaries pulled in by `nest()`: resolved at runtime by `getNesting`\n // through the dictionary registry, never by a `useIntlayer` call site.\n const nestedDictionaryReferences = getNestedDictionaryReferences(\n Object.values(dictionaries)\n );\n\n // Dictionaries holding `nest()` references: their static import is\n // re-pointed at the companion module carrying the nest targets.\n const nestingDictionaryKeys = [\n ...getNestedDictionaryGraph(Object.values(dictionaries)).keys(),\n ];\n\n const isBuildOptimizeEnabled = (\n _config: unknown,\n env: { command: string }\n ) => {\n const isBuildCommand = env.command === 'build';\n return (optimize === undefined && isBuildCommand) || optimize === true;\n };\n\n // Field renaming rewrites the content keys the interpreter walks, so the\n // `keyPath` reported by `ContentSelector` would no longer resolve against\n // the unmerged dictionaries the visual editor edits. Purging and the\n // keyPath-neutral part of minification stay active while the editor is on.\n const isFieldRenameEnabled = !editorEnabled && !!minify;\n\n const isAnalysisEnabled = (_config: unknown, env: { command: string }) =>\n (!!purge || !!minify) && isBuildOptimizeEnabled(_config, env);\n\n // Compat callers the optimize pass cannot bind (`$_('home.title')`, a\n // bare `useI18n()`) still read the runtime registry: emptying it would\n // leave those call sites with no dictionary at all.\n const keepsDictionaryRegistry = requiresDictionaryRegistry(\n compatCallers ?? []\n );\n\n let partiallyMinifiedDictionariesCount = 0;\n\n return [\n intlayerVueAsyncPlugin(intlayerConfig, transformableFilesList),\n\n // Plugin 1: Usage analyser\n {\n name: 'vite-intlayer-usage-analyzer',\n enforce: 'pre',\n apply: isAnalysisEnabled,\n\n buildStart: async () => {\n if (!pruneContext) return;\n\n const extraCallerNames = (compatCallers ?? []).map(\n (caller) => caller.callerName\n );\n const usageCheckRegex = buildUsageCheckRegex(extraCallerNames);\n\n // Phase 1: Babel-based field-usage analysis for all component files\n await Promise.all(\n componentFilesList.map(async (sourceFilePath) => {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return;\n\n let sourceCode: string;\n try {\n sourceCode = await readFile(sourceFilePath, 'utf-8');\n } catch {\n return; // unreadable file – skip silently\n }\n\n if (!usageCheckRegex.test(sourceCode)) return;\n\n // For Vue/Svelte SFCs, the usage analyzer expects the raw script\n // content. `analyzeFieldUsageInFile` handles block extraction\n // internally via `extractScriptBlocks`.\n try {\n await analyzeFieldUsageInFile(\n sourceFilePath,\n sourceCode,\n pruneContext,\n compatCallers\n );\n } catch (parseError) {\n pruneContext.hasUnparsableSourceFiles = true;\n logger(\n [\n `Could not parse`,\n formatPath(sourceFilePath),\n `for field-usage analysis.`,\n 'Dictionaries whose usage cannot be confirmed will not be pruned.',\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n }\n })\n );\n\n // Phase 2: Framework-specific analysis for Vue / Svelte / Astro SFC\n // bindings that Babel scope analysis cannot resolve:\n // Vue → `.value` ref-accessor indirection\n // Svelte → `$` reactive store prefix\n // Astro → frontmatter variables referenced in the HTML template\n if (pruneContext.pendingFrameworkAnalysis.size > 0) {\n const vuePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n const sveltePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n const astroPending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n\n for (const [\n filePath,\n entries,\n ] of pruneContext.pendingFrameworkAnalysis) {\n if (filePath.endsWith('.vue')) {\n vuePending.set(filePath, entries);\n } else if (filePath.endsWith('.svelte')) {\n sveltePending.set(filePath, entries);\n } else if (filePath.endsWith('.astro')) {\n astroPending.set(filePath, entries);\n }\n }\n\n /** Merge framework-extracted field usage into pruneContext. */\n const mergeFrameworkResult = (\n dictionaryKey: string,\n fields: Set<string> | undefined\n ): void => {\n if (fields && fields.size > 0) {\n // The Babel rename plugin cannot update source-code property\n // accesses for SFC indirect patterns → suppress field renaming.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n const existing =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (existing === 'all') return;\n\n const merged =\n existing instanceof Set\n ? new Set([...existing, ...fields])\n : new Set(fields);\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n merged\n );\n } else {\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n 'all'\n );\n }\n };\n\n // Vue files\n if (vuePending.size > 0) {\n let extractVueIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const vueCompiler = await import('@intlayer/vue-compiler');\n extractVueIntlayerFieldUsage =\n vueCompiler.extractVueIntlayerFieldUsage;\n } catch {\n // @intlayer/vue-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of vuePending) {\n if (!extractVueIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractVueIntlayerFieldUsage(fileCode, entries);\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n\n // Svelte files\n if (sveltePending.size > 0) {\n let extractSvelteIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const svelteCompiler = await import(\n '@intlayer/svelte-compiler'\n );\n extractSvelteIntlayerFieldUsage =\n svelteCompiler.extractSvelteIntlayerFieldUsage;\n } catch {\n // @intlayer/svelte-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of sveltePending) {\n if (!extractSvelteIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractSvelteIntlayerFieldUsage(\n fileCode,\n entries\n );\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n\n // Astro files\n // Frontmatter variables are used in the HTML template, which is not\n // visible to Babel's scope analysis. Scan the template section for\n // `variableName.fieldName` accesses using a lightweight regex pass.\n if (astroPending.size > 0) {\n for (const [filePath, entries] of astroPending) {\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n // Extract only the template (everything after the closing ---).\n // The frontmatter was already handled by Babel in Phase 1.\n const fenceMatch = /^---\\r?\\n[\\s\\S]*?\\r?\\n---/.exec(fileCode);\n const template = fenceMatch\n ? fileCode.slice(fenceMatch.index + fenceMatch[0].length)\n : fileCode;\n\n for (const { variableName, dictionaryKey } of entries) {\n const escapedVar = variableName.replace(\n /[.*+?^${}()|[\\]\\\\]/g,\n '\\\\$&'\n );\n const fieldRe = new RegExp(\n `\\\\b${escapedVar}\\\\.([a-zA-Z_$][a-zA-Z0-9_$]*)`,\n 'g'\n );\n const foundFields = new Set<string>();\n let m = fieldRe.exec(template);\n while (m !== null) {\n foundFields.add(m[1]!);\n m = fieldRe.exec(template);\n }\n mergeFrameworkResult(\n dictionaryKey,\n foundFields.size > 0 ? foundFields : undefined\n );\n }\n }\n }\n }\n\n // Phase 2.5: Keep `nest()` targets resolvable — must run before the\n // rename maps of Phase 4 are built.\n preserveNestedDictionaryFields(\n pruneContext,\n nestedDictionaryReferences\n );\n\n // Phase 3: Warn about untracked bindings (plain variable assignments)\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n\n // Phase 4: Build field-rename map for minification\n // Reads each compiled dictionary JSON to discover the full nested\n // user-defined field structure, then builds a NestedRenameMap that\n // assigns short alphabetic aliases at every level.\n if (isFieldRenameEnabled) {\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n\n // Fetch-mode dictionaries are served from a remote API using\n // original field names – renaming would break the client/server\n // contract.\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch')\n continue;\n\n // SFC indirect access: skip field rename for these dictionaries\n // to avoid a JSON ↔ source mismatch at runtime.\n if (\n pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey)\n )\n continue;\n\n // Read dictionary content (static JSON first, then dynamic per-locale)\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(\n dictionariesDir,\n `${dictionaryKey}.json`\n );\n try {\n const raw = await readFile(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n try {\n const dynamicDir = join(\n dynamicDictionariesDir,\n dictionaryKey\n );\n const localeFiles = await readdir(dynamicDir);\n const firstJsonFile = localeFiles.find((f) =>\n f.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = await readFile(\n join(dynamicDir, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary file not readable – skip rename for this key\n }\n }\n\n if (!dictionaryContent) continue;\n\n // Build the rename map from ALL user-defined fields in the\n // dictionary — not just the ones statically consumed by source\n // files. Using the full set ensures that:\n // 1. Every field in the compiled JSON is renamed (even if\n // pruned-out fields still appear when purge is disabled).\n // 2. The short-name assignment is stable: the alphabetical\n // order of all fields determines each short name, so adding\n // or removing a consumer never changes names for others.\n // 3. There is no source ↔ JSON mismatch: both sides use the\n // identical map regardless of which subset is consumed.\n const nestedRenameMap =\n buildNestedRenameMapFromContent(dictionaryContent);\n\n // Skip renaming below opaque values that have nested\n // user-defined structure – renaming those sub-keys would\n // silently break child components that consume the value as-is.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n\n if (opaqueFieldMap) {\n const dangerousOccurrences = [\n ...opaqueFieldMap.values(),\n ].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n )?.children.size ?? 0) > 0\n );\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(\n `'${occurrence.fieldPath.join('.')}'`,\n ANSIColors.BLUE\n ),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (loc) => `\\n at ${formatPath(loc)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n // Disable renaming for the children of opaque values to\n // prevent breaking components that receive the value as a\n // prop.\n for (const occurrence of dangerousOccurrences) {\n const entry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (entry) {\n entry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-partial-minify-summary.lock'\n ),\n () => {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n },\n { cacheTimeoutMs: 1000 * 5 }\n );\n }\n }\n },\n },\n\n // Plugin 2: Babel transform\n {\n name: 'vite-intlayer-babel-transform',\n enforce: 'post', // Run after framework transformations (e.g. Vue SFC)\n apply: (_config, env) => {\n const isBuildCommand = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isBuildCommand || !isEnabled) return false;\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-optimize-plugin-enabled.lock'\n ),\n () => {\n logger([\n `Build optimization ${colorize('enabled', ANSIColors.GREEN)}`,\n colorize('(import mode:', ANSIColors.GREY_DARK),\n colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),\n colorize(')', ANSIColors.GREY_DARK),\n ]);\n\n // The minify plugin reports the field-rename step standing down\n // when the editor is enabled, so this line stays a plain one.\n if (minify) {\n logger(\n `Dictionary minification ${colorize('enabled', ANSIColors.GREEN)}`\n );\n }\n\n if (purge) {\n logger(\n `Dictionary purge unused keys ${colorize('enabled', ANSIColors.GREEN)}`\n );\n }\n },\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n },\n\n transform: async (sourceCode, moduleId, options) => {\n // Strip query parameters added by Vue/Svelte loaders\n // e.g. \"HelloWorld.vue?vue&type=script&setup=true&lang.ts\" → \"HelloWorld.vue\"\n const sourceFilePath = normalizePath(\n moduleId.split('?', 1)[0] ?? moduleId\n );\n\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;\n if (!transformableFilesList.includes(sourceFilePath)) return null;\n\n const isDictionaryEntryFile =\n sourceFilePath === dictionariesEntryPath;\n\n // Match native intlayer calls plus any configured compat callers\n // (`useTranslation`, `useTranslations`, …) so compat-only files are\n // still optimized.\n const optimizeUsageRegex = compatCallers?.length\n ? buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n )\n : INTLAYER_USAGE_REGEX;\n\n const isUsingIntlayer = optimizeUsageRegex.test(sourceCode);\n if (!isUsingIntlayer && !isDictionaryEntryFile) return null;\n\n // Field rename + optimize (useIntlayer('key') → useDictionary(_hash))\n // in a single Babel pass: the rename runs first, before the optimize\n // step erases the dictionary key it keys off.\n const fieldRenamePruneContext =\n isFieldRenameEnabled && pruneContext && isUsingIntlayer\n ? pruneContext\n : undefined;\n\n const transformResult = await optimizeSourceFile(\n sourceCode,\n sourceFilePath,\n {\n optimize,\n dictionariesDir,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n unmergedDictionariesDir,\n dynamicDictionariesDir,\n dynamicDictionariesEntryPath,\n fetchDictionariesDir,\n fetchDictionariesEntryPath: join(\n mainDir,\n 'fetch_dictionaries.mjs'\n ),\n importMode,\n filesList: transformableFilesList,\n replaceDictionaryEntry:\n isBuildOptimizeEnabled(null, { command: 'build' }) &&\n !keepsDictionaryRegistry,\n nestingDictionaryKeys,\n dictionaryModeMap: dictionaryKeyToImportModeMap,\n isServer: options?.ssr === true,\n compatCallers,\n },\n fieldRenamePruneContext\n );\n\n if (!transformResult) return null;\n\n return {\n code: transformResult.code,\n map: transformResult.map as any,\n };\n },\n },\n ];\n } catch (pluginInitError) {\n console.warn(\n '[vite-intlayer] Failed to initialise optimization plugin:',\n pluginInitError\n );\n return [];\n }\n};\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { intlayerChunk } from "./intlayerChunkPlugin.mjs";
2
- import { startContentWatcher } from "./startContentWatcher.mjs";
3
2
  import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
4
3
  import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
5
4
  import { intlayerCompiler } from "./IntlayerCompilerPlugin.mjs";
@@ -11,6 +10,7 @@ import { getAlias, getHasDictionarySelector, getUnusedNodeTypesAsync } from "@in
11
10
  import { resolveProxyMode } from "@intlayer/core/localization";
12
11
  import { resolve } from "node:path";
13
12
  import { BLUE } from "@intlayer/config/colors";
13
+ import { startContentWatcher } from "@intlayer/engine/utils";
14
14
  import { createPruneContext } from "@intlayer/babel";
15
15
  import { getDictionaries } from "@intlayer/dictionaries-entry";
16
16
  import { formatDictionarySelectorEnvVar, formatNodeTypeToEnvVar, formatOptimizedNestingEnvVar, getConfigEnvVars } from "@intlayer/config/envVars";
@@ -108,7 +108,7 @@ const intlayerPlugin = (configOptions) => {
108
108
  };
109
109
  },
110
110
  configureServer: (server) => {
111
- if (server.config.mode === "development") startContentWatcher(intlayerConfig);
111
+ if (server.config.mode === "development") startContentWatcher(intlayerConfig, { label: "vite-intlayer" });
112
112
  }
113
113
  }];
114
114
  const pruneContext = createPruneContext();
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { type CompatCallerConfig, createPruneContext } from '@intlayer/babel';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatDictionarySelectorEnvVar,\n formatNodeTypeToEnvVar,\n formatOptimizedNestingEnvVar,\n getConfigEnvVars,\n} from '@intlayer/config/envVars';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getAlias,\n getHasDictionarySelector,\n getUnusedNodeTypesAsync,\n} from '@intlayer/config/utils';\nimport { resolveProxyMode } from '@intlayer/core/localization';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { logConfigDetails } from '@intlayer/engine/logConfigDetails';\nimport type { PluginOption } from 'vite';\nimport { intlayerCompiler } from './IntlayerCompilerPlugin';\nimport { intlayerChunk } from './intlayerChunkPlugin';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport { intlayerPreload } from './intlayerPreloadPlugin';\nimport {\n type IntlayerProxyPluginOptions,\n intlayerProxy,\n} from './intlayerProxyPlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\nimport { startContentWatcher } from './startContentWatcher';\n\n/**\n * Packages that must go through Vite's transform pipeline instead of being\n * loaded natively by Node.\n *\n * `intlayer` and every `@intlayer/*` package resolve `@intlayer/dictionaries-entry`\n * through a Vite alias pointing at the generated `.intlayer` entry. A package\n * loaded natively by Node never sees that alias: it resolves the published stub\n * instead, whose CJS `require` of the generated dictionaries is cached for the\n * lifetime of the process — so content edits stop reaching the running server.\n */\nexport const INTLAYER_NO_EXTERNAL_PATTERN = /(^@intlayer\\/|intlayer$)/;\n\n/**\n * Extended options accepted by the intlayer Vite plugin.\n *\n * Extends {@link GetConfigurationOptions} with compat-adapter caller\n * configurations. Compat adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) inject their own caller configs here so the\n * field-usage analyser can recognise their translation function call patterns\n * and prune unused dictionary fields accordingly.\n */\nexport type IntlayerPluginOptions = GetConfigurationOptions & {\n /**\n * Compat-adapter namespace caller configurations to pass to the\n * field-usage analyser (Vite `buildStart` phase).\n *\n * Defined by each compat adapter package; the core `vite-intlayer` package\n * ships with an empty default to stay framework-agnostic.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Options forwarded to the bundled locale-routing proxy (`intlayerProxy`).\n *\n * Since Intlayer v9 the proxy is plugged directly into the main `intlayer()`\n * plugin (controlled by `routing.enableProxy`, unset by default, which\n * selects auto mode). These routing options let you tune that bundled proxy\n * without registering it separately — e.g. ignore API routes from locale\n * routing.\n *\n * @example\n * ```ts\n * intlayer({ proxy: { ignore: (req) => req.url?.startsWith('/api') } })\n * ```\n */\n proxy?: Pick<IntlayerProxyPluginOptions, 'ignore'>;\n};\n\n/**\n * Vite plugin that integrates Intlayer into the Vite build process.\n *\n * It handles:\n * 1. Preparing Intlayer resources (dictionaries) before build.\n * 2. Configuring Vite aliases for dictionary access.\n * 3. Setting up dev-server watchers for content changes.\n * 4. Applying build optimizations (tree-shaking dictionaries).\n *\n * @param configOptions - Optional configuration to override default Intlayer settings.\n * @returns A Vite plugin option.\n *\n * @example\n * ```ts\n * import { intlayer } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayer()],\n * });\n *\n * ```\n * @deprecated Rename to intlayer instead\n */\nexport const intlayerPlugin = (\n configOptions?: IntlayerPluginOptions\n): PluginOption => {\n const { compatCallers, proxy, ...getConfigOptions } = configOptions ?? {};\n const intlayerConfig = getConfiguration(getConfigOptions);\n logConfigDetails(getConfigOptions);\n const appLogger = getAppLogger(intlayerConfig);\n\n const alias = getAlias({\n configuration: intlayerConfig,\n formatter: (value: string) => resolve(value),\n });\n\n const aliasPackages = Object.keys(alias);\n\n const plugins: PluginOption[] = [\n {\n name: 'vite-intlayer-plugin',\n\n apply: (_config, env) => {\n // Don't apply intlayer plugin during `preview` command\n const isPreviewCommand =\n env.command === 'serve' && env.mode === 'production';\n\n // But if liveSync is enabled, ensure the data are fresh\n const isLiveSyncEnabled = intlayerConfig.editor.liveSync;\n\n return !isPreviewCommand || isLiveSyncEnabled;\n },\n\n config: async (_config, env) => {\n const { mode } = intlayerConfig.build;\n\n const isDevCommand =\n env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Only call prepareIntlayer during `dev` or `build` (not during `preview`)\n // If prod: clean and rebuild once\n // If dev: rebuild only once if it's more than 1 hour since last rebuild\n if (isDevCommand || isBuildCommand || mode === 'auto') {\n // Lazily load the heavy build pipeline (`@intlayer/engine/build`) only\n // when a rebuild is actually required, keeping the Vite config phase\n // lightweight (e.g. during `preview`).\n const { prepareIntlayer } = await import('@intlayer/engine/build');\n\n // prepareIntlayer use runOnce to ensure to run only once because will run twice on client and server side otherwise\n await prepareIntlayer(intlayerConfig, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build (to ensure to rebuild all dictionaries)\n : 1000 * 60 * 60, // 1 hour for dev (default cache timeout)\n env: isBuildCommand ? 'prod' : 'dev',\n });\n }\n\n let define: Record<string, string> = {\n // Preset an env var to avoid 'process is not defined' error\n // Needed for some libraries that does not add process.env\n 'process.env.INTLAYER': '\"true\"',\n };\n\n if (isBuildCommand) {\n const dictionaries = getDictionaries(intlayerConfig);\n\n // Mirrors the optimize plugin's own `apply` condition: the local\n // `nest()` resolver may only be selected when that transform runs,\n // since it is the transform that injects the nest attachment.\n const isOptimizeEnabled = intlayerConfig.build.optimize !== false;\n\n if (Object.keys(dictionaries).length === 0) {\n appLogger(\n 'No dictionaries found. Please check your configuration.',\n {\n isVerbose: true,\n }\n );\n }\n\n const unusedNodeTypes = await getUnusedNodeTypesAsync(dictionaries);\n\n if (unusedNodeTypes.length > 0) {\n appLogger(\n [\n 'Filtering out unused logic:',\n unusedNodeTypes\n .filter(\n (key) =>\n !['reactNode', 'solidNode', 'preactNode'].includes(key)\n )\n .map((key) => colorize(key, BLUE))\n .join(', '),\n ],\n {\n isVerbose: true,\n }\n );\n }\n\n define = {\n ...define,\n\n // Tree shacking env var based on config\n ...formatNodeTypeToEnvVar(\n unusedNodeTypes,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Selects the local `nest()` resolver, which reads the nest\n // targets attached to each dictionary by the optimize transform\n // instead of the registry that transform empties.\n ...formatOptimizedNestingEnvVar(\n isOptimizeEnabled,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var for the dictionary selector logic\n // (collections / variants)\n ...formatDictionarySelectorEnvVar(\n getHasDictionarySelector(dictionaries),\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var based on config\n ...getConfigEnvVars(\n intlayerConfig,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"` // Wrap by \"\" to ensure env var set properly\n ),\n };\n }\n\n // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return {\n define,\n resolve: {\n alias,\n },\n optimizeDeps: {\n // Exclude alias entry points since they're local files, not npm packages\n exclude: aliasPackages,\n },\n ssr: {\n // Ensure intlayer packages are bundled so aliases are applied\n noExternal: [INTLAYER_NO_EXTERNAL_PATTERN],\n },\n };\n },\n\n configureServer: (server) => {\n if (server.config.mode === 'development') {\n // Takes the content watcher lock first, so a parallel\n // `intlayer watch` (or the VS Code extension) does not rebuild the\n // same dictionaries alongside this dev server.\n startContentWatcher(intlayerConfig);\n }\n },\n },\n ];\n\n // Shared mutable state: the optimize plugin writes field-usage data during\n // buildStart; the prune and minify plugins read it during transform.\n const pruneContext = createPruneContext();\n\n // Babel transform: rewrites useIntlayer/getIntlayer calls and injects\n // JSON / dynamic-mjs imports. Also runs the usage analyser in buildStart.\n plugins.push(intlayerOptimize(intlayerConfig, pruneContext, compatCallers));\n\n // Prune: removes unused content fields from dictionary JSON files.\n // Runs with enforce:'pre' so it intercepts raw JSON before Vite's\n // built-in JSON → ESM conversion.\n plugins.push(intlayerPrune(intlayerConfig, pruneContext));\n\n // Minify: compacts dictionary JSON files (parse + re-stringify).\n // Registered after prune so it receives already-pruned output when both options are active.\n plugins.push(intlayerMinify(intlayerConfig, pruneContext));\n\n // Chunk: regroups the per-dictionary chunks that `importMode: 'dynamic'`\n // emits, so each code-split boundary loads its content in one request.\n plugins.push(intlayerChunk(intlayerConfig));\n\n // Preload: makes each dynamic entry point await its locale's dictionary while\n // it evaluates, so the content travels with the chunk that needs it instead\n // of being fetched — and suspended on — once that chunk renders.\n plugins.push(intlayerPreload(intlayerConfig));\n\n // Compiler: extracts content declared inline in components into dictionaries.\n // Bundled directly into the main plugin so users no longer need to register\n // `intlayerCompiler()` separately. Only added when the compiler is enabled and\n // an `output` path is configured. Registering `intlayerCompiler()` manually as\n // well is safe — the compiler deduplicates itself.\n if (intlayerConfig.compiler?.enabled && intlayerConfig.compiler?.output) {\n plugins.push(intlayerCompiler({ configOptions: getConfigOptions }));\n }\n\n // Proxy: locale-routing middleware (detection / redirect / rewrite) for dev,\n // preview, and production SSR. Bundled directly into the main plugin and\n // controlled by the `routing.enableProxy` option, which is unset by default\n // and therefore resolves to auto mode. Only an explicit `false` skips\n // registration — a plain truthiness check would wrongly drop the proxy for\n // every project that never set the option.\n // Registering `intlayerProxy()` manually as well is safe — the proxy\n // deduplicates itself.\n if (resolveProxyMode(intlayerConfig.routing.enableProxy) !== 'disabled') {\n plugins.push(intlayerProxy({ ...proxy, configOptions: getConfigOptions }));\n }\n\n return plugins;\n};\n\n/**\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intlayer = intlayerPlugin;\n/**\n * @deprecated Rename to intlayer instead\n *\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intLayerPlugin = intlayerPlugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { type CompatCallerConfig, createPruneContext } from '@intlayer/babel';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatDictionarySelectorEnvVar,\n formatNodeTypeToEnvVar,\n formatOptimizedNestingEnvVar,\n getConfigEnvVars,\n} from '@intlayer/config/envVars';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getAlias,\n getHasDictionarySelector,\n getUnusedNodeTypesAsync,\n} from '@intlayer/config/utils';\nimport { resolveProxyMode } from '@intlayer/core/localization';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { logConfigDetails } from '@intlayer/engine/logConfigDetails';\nimport { startContentWatcher } from '@intlayer/engine/utils';\nimport type { PluginOption } from 'vite';\nimport { intlayerCompiler } from './IntlayerCompilerPlugin';\nimport { intlayerChunk } from './intlayerChunkPlugin';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport { intlayerPreload } from './intlayerPreloadPlugin';\nimport {\n type IntlayerProxyPluginOptions,\n intlayerProxy,\n} from './intlayerProxyPlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\n\n/**\n * Packages that must go through Vite's transform pipeline instead of being\n * loaded natively by Node.\n *\n * `intlayer` and every `@intlayer/*` package resolve `@intlayer/dictionaries-entry`\n * through a Vite alias pointing at the generated `.intlayer` entry. A package\n * loaded natively by Node never sees that alias: it resolves the published stub\n * instead, whose CJS `require` of the generated dictionaries is cached for the\n * lifetime of the process — so content edits stop reaching the running server.\n */\nexport const INTLAYER_NO_EXTERNAL_PATTERN = /(^@intlayer\\/|intlayer$)/;\n\n/**\n * Extended options accepted by the intlayer Vite plugin.\n *\n * Extends {@link GetConfigurationOptions} with compat-adapter caller\n * configurations. Compat adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) inject their own caller configs here so the\n * field-usage analyser can recognise their translation function call patterns\n * and prune unused dictionary fields accordingly.\n */\nexport type IntlayerPluginOptions = GetConfigurationOptions & {\n /**\n * Compat-adapter namespace caller configurations to pass to the\n * field-usage analyser (Vite `buildStart` phase).\n *\n * Defined by each compat adapter package; the core `vite-intlayer` package\n * ships with an empty default to stay framework-agnostic.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Options forwarded to the bundled locale-routing proxy (`intlayerProxy`).\n *\n * Since Intlayer v9 the proxy is plugged directly into the main `intlayer()`\n * plugin (controlled by `routing.enableProxy`, unset by default, which\n * selects auto mode). These routing options let you tune that bundled proxy\n * without registering it separately — e.g. ignore API routes from locale\n * routing.\n *\n * @example\n * ```ts\n * intlayer({ proxy: { ignore: (req) => req.url?.startsWith('/api') } })\n * ```\n */\n proxy?: Pick<IntlayerProxyPluginOptions, 'ignore'>;\n};\n\n/**\n * Vite plugin that integrates Intlayer into the Vite build process.\n *\n * It handles:\n * 1. Preparing Intlayer resources (dictionaries) before build.\n * 2. Configuring Vite aliases for dictionary access.\n * 3. Setting up dev-server watchers for content changes.\n * 4. Applying build optimizations (tree-shaking dictionaries).\n *\n * @param configOptions - Optional configuration to override default Intlayer settings.\n * @returns A Vite plugin option.\n *\n * @example\n * ```ts\n * import { intlayer } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayer()],\n * });\n *\n * ```\n * @deprecated Rename to intlayer instead\n */\nexport const intlayerPlugin = (\n configOptions?: IntlayerPluginOptions\n): PluginOption => {\n const { compatCallers, proxy, ...getConfigOptions } = configOptions ?? {};\n const intlayerConfig = getConfiguration(getConfigOptions);\n logConfigDetails(getConfigOptions);\n const appLogger = getAppLogger(intlayerConfig);\n\n const alias = getAlias({\n configuration: intlayerConfig,\n formatter: (value: string) => resolve(value),\n });\n\n const aliasPackages = Object.keys(alias);\n\n const plugins: PluginOption[] = [\n {\n name: 'vite-intlayer-plugin',\n\n apply: (_config, env) => {\n // Don't apply intlayer plugin during `preview` command\n const isPreviewCommand =\n env.command === 'serve' && env.mode === 'production';\n\n // But if liveSync is enabled, ensure the data are fresh\n const isLiveSyncEnabled = intlayerConfig.editor.liveSync;\n\n return !isPreviewCommand || isLiveSyncEnabled;\n },\n\n config: async (_config, env) => {\n const { mode } = intlayerConfig.build;\n\n const isDevCommand =\n env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Only call prepareIntlayer during `dev` or `build` (not during `preview`)\n // If prod: clean and rebuild once\n // If dev: rebuild only once if it's more than 1 hour since last rebuild\n if (isDevCommand || isBuildCommand || mode === 'auto') {\n // Lazily load the heavy build pipeline (`@intlayer/engine/build`) only\n // when a rebuild is actually required, keeping the Vite config phase\n // lightweight (e.g. during `preview`).\n const { prepareIntlayer } = await import('@intlayer/engine/build');\n\n // prepareIntlayer use runOnce to ensure to run only once because will run twice on client and server side otherwise\n await prepareIntlayer(intlayerConfig, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build (to ensure to rebuild all dictionaries)\n : 1000 * 60 * 60, // 1 hour for dev (default cache timeout)\n env: isBuildCommand ? 'prod' : 'dev',\n });\n }\n\n let define: Record<string, string> = {\n // Preset an env var to avoid 'process is not defined' error\n // Needed for some libraries that does not add process.env\n 'process.env.INTLAYER': '\"true\"',\n };\n\n if (isBuildCommand) {\n const dictionaries = getDictionaries(intlayerConfig);\n\n // Mirrors the optimize plugin's own `apply` condition: the local\n // `nest()` resolver may only be selected when that transform runs,\n // since it is the transform that injects the nest attachment.\n const isOptimizeEnabled = intlayerConfig.build.optimize !== false;\n\n if (Object.keys(dictionaries).length === 0) {\n appLogger(\n 'No dictionaries found. Please check your configuration.',\n {\n isVerbose: true,\n }\n );\n }\n\n const unusedNodeTypes = await getUnusedNodeTypesAsync(dictionaries);\n\n if (unusedNodeTypes.length > 0) {\n appLogger(\n [\n 'Filtering out unused logic:',\n unusedNodeTypes\n .filter(\n (key) =>\n !['reactNode', 'solidNode', 'preactNode'].includes(key)\n )\n .map((key) => colorize(key, BLUE))\n .join(', '),\n ],\n {\n isVerbose: true,\n }\n );\n }\n\n define = {\n ...define,\n\n // Tree shacking env var based on config\n ...formatNodeTypeToEnvVar(\n unusedNodeTypes,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Selects the local `nest()` resolver, which reads the nest\n // targets attached to each dictionary by the optimize transform\n // instead of the registry that transform empties.\n ...formatOptimizedNestingEnvVar(\n isOptimizeEnabled,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var for the dictionary selector logic\n // (collections / variants)\n ...formatDictionarySelectorEnvVar(\n getHasDictionarySelector(dictionaries),\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var based on config\n ...getConfigEnvVars(\n intlayerConfig,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"` // Wrap by \"\" to ensure env var set properly\n ),\n };\n }\n\n // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return {\n define,\n resolve: {\n alias,\n },\n optimizeDeps: {\n // Exclude alias entry points since they're local files, not npm packages\n exclude: aliasPackages,\n },\n ssr: {\n // Ensure intlayer packages are bundled so aliases are applied\n noExternal: [INTLAYER_NO_EXTERNAL_PATTERN],\n },\n };\n },\n\n configureServer: (server) => {\n if (server.config.mode === 'development') {\n // Takes the content watcher lock first, so a parallel\n // `intlayer watch` (or the VS Code extension) does not rebuild the\n // same dictionaries alongside this dev server.\n startContentWatcher(intlayerConfig, { label: 'vite-intlayer' });\n }\n },\n },\n ];\n\n // Shared mutable state: the optimize plugin writes field-usage data during\n // buildStart; the prune and minify plugins read it during transform.\n const pruneContext = createPruneContext();\n\n // Babel transform: rewrites useIntlayer/getIntlayer calls and injects\n // JSON / dynamic-mjs imports. Also runs the usage analyser in buildStart.\n plugins.push(intlayerOptimize(intlayerConfig, pruneContext, compatCallers));\n\n // Prune: removes unused content fields from dictionary JSON files.\n // Runs with enforce:'pre' so it intercepts raw JSON before Vite's\n // built-in JSON → ESM conversion.\n plugins.push(intlayerPrune(intlayerConfig, pruneContext));\n\n // Minify: compacts dictionary JSON files (parse + re-stringify).\n // Registered after prune so it receives already-pruned output when both options are active.\n plugins.push(intlayerMinify(intlayerConfig, pruneContext));\n\n // Chunk: regroups the per-dictionary chunks that `importMode: 'dynamic'`\n // emits, so each code-split boundary loads its content in one request.\n plugins.push(intlayerChunk(intlayerConfig));\n\n // Preload: makes each dynamic entry point await its locale's dictionary while\n // it evaluates, so the content travels with the chunk that needs it instead\n // of being fetched — and suspended on — once that chunk renders.\n plugins.push(intlayerPreload(intlayerConfig));\n\n // Compiler: extracts content declared inline in components into dictionaries.\n // Bundled directly into the main plugin so users no longer need to register\n // `intlayerCompiler()` separately. Only added when the compiler is enabled and\n // an `output` path is configured. Registering `intlayerCompiler()` manually as\n // well is safe — the compiler deduplicates itself.\n if (intlayerConfig.compiler?.enabled && intlayerConfig.compiler?.output) {\n plugins.push(intlayerCompiler({ configOptions: getConfigOptions }));\n }\n\n // Proxy: locale-routing middleware (detection / redirect / rewrite) for dev,\n // preview, and production SSR. Bundled directly into the main plugin and\n // controlled by the `routing.enableProxy` option, which is unset by default\n // and therefore resolves to auto mode. Only an explicit `false` skips\n // registration — a plain truthiness check would wrongly drop the proxy for\n // every project that never set the option.\n // Registering `intlayerProxy()` manually as well is safe — the proxy\n // deduplicates itself.\n if (resolveProxyMode(intlayerConfig.routing.enableProxy) !== 'disabled') {\n plugins.push(intlayerProxy({ ...proxy, configOptions: getConfigOptions }));\n }\n\n return plugins;\n};\n\n/**\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intlayer = intlayerPlugin;\n/**\n * @deprecated Rename to intlayer instead\n *\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intLayerPlugin = intlayerPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAa,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;AA6D5C,MAAa,kBACX,kBACiB;CACjB,MAAM,EAAE,eAAe,OAAO,GAAG,qBAAqB,iBAAiB,CAAC;CACxE,MAAM,iBAAiB,iBAAiB,gBAAgB;CACxD,iBAAiB,gBAAgB;CACjC,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,QAAQ,SAAS;EACrB,eAAe;EACf,YAAY,UAAkB,QAAQ,KAAK;CAC7C,CAAC;CAED,MAAM,gBAAgB,OAAO,KAAK,KAAK;CAEvC,MAAM,UAA0B,CAC9B;EACE,MAAM;EAEN,QAAQ,SAAS,QAAQ;GAEvB,MAAM,mBACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAG1C,MAAM,oBAAoB,eAAe,OAAO;GAEhD,OAAO,CAAC,oBAAoB;EAC9B;EAEA,QAAQ,OAAO,SAAS,QAAQ;GAC9B,MAAM,EAAE,SAAS,eAAe;GAEhC,MAAM,eACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAC1C,MAAM,iBAAiB,IAAI,YAAY;GAKvC,IAAI,gBAAgB,kBAAkB,SAAS,QAAQ;IAIrD,MAAM,EAAE,oBAAoB,MAAM,OAAO;IAGzC,MAAM,gBAAgB,gBAAgB;KACpC,OAAO;KACP,gBAAgB,iBACZ,MACA;KACJ,KAAK,iBAAiB,SAAS;IACjC,CAAC;GACH;GAEA,IAAI,SAAiC,EAGnC,wBAAwB,WAC1B;GAEA,IAAI,gBAAgB;IAClB,MAAM,eAAe,gBAAgB,cAAc;IAKnD,MAAM,oBAAoB,eAAe,MAAM,aAAa;IAE5D,IAAI,OAAO,KAAK,YAAY,CAAC,CAAC,WAAW,GACvC,UACE,2DACA,EACE,WAAW,KACb,CACF;IAGF,MAAM,kBAAkB,MAAM,wBAAwB,YAAY;IAElE,IAAI,gBAAgB,SAAS,GAC3B,UACE,CACE,+BACA,gBACG,QACE,QACC,CAAC;KAAC;KAAa;KAAa;IAAY,CAAC,CAAC,SAAS,GAAG,CAC1D,CAAC,CACA,KAAK,QAAQ,SAAS,KAAK,IAAI,CAAC,CAAC,CACjC,KAAK,IAAI,CACd,GACA,EACE,WAAW,KACb,CACF;IAGF,SAAS;KACP,GAAG;KAGH,GAAG,uBACD,kBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAKA,GAAG,6BACD,oBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAIA,GAAG,+BACD,yBAAyB,YAAY,IACpC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAGA,GAAG,iBACD,iBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;IACF;GACF;GAIA,OAAO;IACL;IACA,SAAS,EACP,MACF;IACA,cAAc,EAEZ,SAAS,cACX;IACA,KAAK,EAEH,YAAY,CAAC,4BAA4B,EAC3C;GACF;EACF;EAEA,kBAAkB,WAAW;GAC3B,IAAI,OAAO,OAAO,SAAS,eAIzB,oBAAoB,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;EAElE;CACF,CACF;CAIA,MAAM,eAAe,mBAAmB;CAIxC,QAAQ,KAAK,iBAAiB,gBAAgB,cAAc,aAAa,CAAC;CAK1E,QAAQ,KAAK,cAAc,gBAAgB,YAAY,CAAC;CAIxD,QAAQ,KAAK,eAAe,gBAAgB,YAAY,CAAC;CAIzD,QAAQ,KAAK,cAAc,cAAc,CAAC;CAK1C,QAAQ,KAAK,gBAAgB,cAAc,CAAC;CAO5C,IAAI,eAAe,UAAU,WAAW,eAAe,UAAU,QAC/D,QAAQ,KAAK,iBAAiB,EAAE,eAAe,iBAAiB,CAAC,CAAC;CAWpE,IAAI,iBAAiB,eAAe,QAAQ,WAAW,MAAM,YAC3D,QAAQ,KAAK,cAAc;EAAE,GAAG;EAAO,eAAe;CAAiB,CAAC,CAAC;CAG3E,OAAO;AACT;;;;;;;;;;;AAYA,MAAa,WAAW;;;;;;;;;;;;;AAaxB,MAAa,iBAAiB"}
@@ -1,7 +1,7 @@
1
1
  import { colorize, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
2
- import { formatPath, runOnce } from "@intlayer/engine/utils";
3
2
  import { join } from "node:path";
4
3
  import * as ANSIColors from "@intlayer/config/colors";
4
+ import { formatPath, runOnce } from "@intlayer/engine/utils";
5
5
 
6
6
  //#region src/intlayerPrunePlugin.ts
7
7
  const isTranslationNode = (value) => typeof value === "object" && value !== null && value.nodeType === "translation" && typeof value.translation === "object";
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerOptimizePlugin.d.ts","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;qBAgEa,mBAAgB,gBACX,gBAAc,cAChB,qBAAmB,gBACjB,uBACf,QAAQ"}
1
+ {"version":3,"file":"intlayerOptimizePlugin.d.ts","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;qBA+Da,mBAAgB,gBACX,gBAAc,cAChB,qBAAmB,gBACjB,uBACf,QAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-intlayer",
3
- "version": "9.5.11",
3
+ "version": "9.5.12",
4
4
  "private": false,
5
5
  "description": "A Vite plugin for seamless internationalization (i18n), providing locale detection, redirection, and environment-based configuration",
6
6
  "keywords": [
@@ -87,12 +87,12 @@
87
87
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
88
88
  },
89
89
  "dependencies": {
90
- "@intlayer/babel": "9.5.11",
91
- "@intlayer/config": "9.5.11",
92
- "@intlayer/core": "9.5.11",
93
- "@intlayer/dictionaries-entry": "9.5.11",
94
- "@intlayer/engine": "9.5.11",
95
- "@intlayer/types": "9.5.11"
90
+ "@intlayer/babel": "9.5.12",
91
+ "@intlayer/config": "9.5.12",
92
+ "@intlayer/core": "9.5.12",
93
+ "@intlayer/dictionaries-entry": "9.5.12",
94
+ "@intlayer/engine": "9.5.12",
95
+ "@intlayer/types": "9.5.12"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@types/node": "^22",
@@ -106,8 +106,8 @@
106
106
  },
107
107
  "peerDependencies": {
108
108
  "@babel/core": ">=6.0.0",
109
- "@intlayer/svelte-compiler": "9.5.11",
110
- "@intlayer/vue-compiler": "9.5.11",
109
+ "@intlayer/svelte-compiler": "9.5.12",
110
+ "@intlayer/vue-compiler": "9.5.12",
111
111
  "h3": ">=1.0.0",
112
112
  "vite": ">=4.0.0"
113
113
  },
@@ -1,81 +0,0 @@
1
- import { getAppLogger } from "@intlayer/config/logger";
2
- import { acquireContentWatcherLock, getCliContentWatcherLabel, getContentWatcherOwner, reportRedundantContentWatcher } from "@intlayer/engine/utils";
3
-
4
- //#region src/startContentWatcher.ts
5
- /** How the Vite integration names itself in the lock and in messages. */
6
- const BUNDLER_LABEL = "vite-intlayer";
7
- /**
8
- * How often a process that lost the ownership race checks whether the owner is
9
- * gone, so a second dev server can take over once the first one stops.
10
- */
11
- const TAKEOVER_POLL_INTERVAL_MS = 5e3;
12
- /** Set once the watcher is running in this process. */
13
- let isWatching = false;
14
- /**
15
- * Set while an ownership attempt is in flight.
16
- *
17
- * `configureServer` runs again on every Vite server restart (e.g. after a
18
- * config edit), and taking the lock is asynchronous — without this, restarts
19
- * would pile up concurrent attempts.
20
- */
21
- let isClaimInFlight = false;
22
- /** Reports a redundant `intlayer watch` running alongside this dev server. */
23
- const reportRedundantCliWatcher = (configuration, cliWatcherLabel) => reportRedundantContentWatcher(configuration, {
24
- cliLabel: cliWatcherLabel,
25
- bundlerLabel: BUNDLER_LABEL
26
- });
27
- const claimAndWatch = async (configuration) => {
28
- const appLogger = getAppLogger(configuration);
29
- if (!await acquireContentWatcherLock(configuration, {
30
- source: "bundler",
31
- label: BUNDLER_LABEL
32
- })) {
33
- const owner = await getContentWatcherOwner(configuration);
34
- if (owner?.source === "cli") {
35
- reportRedundantCliWatcher(configuration, owner.label);
36
- return;
37
- }
38
- setTimeout(() => {
39
- startContentWatcher(configuration);
40
- }, TAKEOVER_POLL_INTERVAL_MS).unref?.();
41
- return;
42
- }
43
- isWatching = true;
44
- try {
45
- const { watch } = await import("@intlayer/engine/watcher");
46
- await watch({ configuration });
47
- } catch (error) {
48
- isWatching = false;
49
- appLogger(["Failed to watch Intlayer content declarations:", error], { level: "error" });
50
- }
51
- };
52
- /**
53
- * Starts the content declaration watcher for a Vite dev server.
54
- *
55
- * Takes the project's content watcher lock first, so only one process rebuilds
56
- * the dictionaries: it stands down — with an explanation — when `intlayer watch`
57
- * is already running, and lets other tools (e.g. the VS Code extension) see that
58
- * a watcher covers the project.
59
- *
60
- * Deliberately not awaited by the caller: the dev server must not wait on the
61
- * watcher to start serving.
62
- *
63
- * @param configuration - The resolved Intlayer configuration.
64
- */
65
- const startContentWatcher = (configuration) => {
66
- if (isWatching || isClaimInFlight) return;
67
- if (!configuration.content.watch) return;
68
- const cliWatcherLabel = getCliContentWatcherLabel();
69
- if (cliWatcherLabel) {
70
- reportRedundantCliWatcher(configuration, cliWatcherLabel);
71
- return;
72
- }
73
- isClaimInFlight = true;
74
- claimAndWatch(configuration).finally(() => {
75
- isClaimInFlight = false;
76
- });
77
- };
78
-
79
- //#endregion
80
- export { startContentWatcher };
81
- //# sourceMappingURL=startContentWatcher.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"startContentWatcher.mjs","names":[],"sources":["../../src/startContentWatcher.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport {\n acquireContentWatcherLock,\n getCliContentWatcherLabel,\n getContentWatcherOwner,\n reportRedundantContentWatcher,\n} from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/** How the Vite integration names itself in the lock and in messages. */\nconst BUNDLER_LABEL = 'vite-intlayer';\n\n/**\n * How often a process that lost the ownership race checks whether the owner is\n * gone, so a second dev server can take over once the first one stops.\n */\nconst TAKEOVER_POLL_INTERVAL_MS = 5000;\n\n/** Set once the watcher is running in this process. */\nlet isWatching = false;\n\n/**\n * Set while an ownership attempt is in flight.\n *\n * `configureServer` runs again on every Vite server restart (e.g. after a\n * config edit), and taking the lock is asynchronous — without this, restarts\n * would pile up concurrent attempts.\n */\nlet isClaimInFlight = false;\n\n/** Reports a redundant `intlayer watch` running alongside this dev server. */\nconst reportRedundantCliWatcher = (\n configuration: IntlayerConfig,\n cliWatcherLabel: string\n): void =>\n reportRedundantContentWatcher(configuration, {\n cliLabel: cliWatcherLabel,\n bundlerLabel: BUNDLER_LABEL,\n });\n\nconst claimAndWatch = async (configuration: IntlayerConfig): Promise<void> => {\n const appLogger = getAppLogger(configuration);\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'bundler',\n label: BUNDLER_LABEL,\n });\n\n if (!hasAcquiredLock) {\n // A CLI watcher started from another terminal is just as redundant as one\n // wrapping this command, and worth the same explanation.\n const owner = await getContentWatcherOwner(configuration);\n\n if (owner?.source === 'cli') {\n reportRedundantCliWatcher(configuration, owner.label);\n return;\n }\n\n // Another dev server owns the watcher: retry in the background so this one\n // picks it up if that server stops first.\n const retryTimer = setTimeout(() => {\n startContentWatcher(configuration);\n }, TAKEOVER_POLL_INTERVAL_MS);\n\n // Unreferenced so a process whose only remaining work is this retry can\n // still exit.\n retryTimer.unref?.();\n\n return;\n }\n\n isWatching = true;\n\n try {\n // Imported lazily: `@intlayer/engine/watcher` pulls in `@parcel/watcher`, a\n // native module only needed once the dev server actually starts.\n const { watch } = await import('@intlayer/engine/watcher');\n\n await watch({ configuration });\n } catch (error) {\n isWatching = false;\n\n appLogger(['Failed to watch Intlayer content declarations:', error], {\n level: 'error',\n });\n }\n};\n\n/**\n * Starts the content declaration watcher for a Vite dev server.\n *\n * Takes the project's content watcher lock first, so only one process rebuilds\n * the dictionaries: it stands down — with an explanation — when `intlayer watch`\n * is already running, and lets other tools (e.g. the VS Code extension) see that\n * a watcher covers the project.\n *\n * Deliberately not awaited by the caller: the dev server must not wait on the\n * watcher to start serving.\n *\n * @param configuration - The resolved Intlayer configuration.\n */\nexport const startContentWatcher = (configuration: IntlayerConfig): void => {\n if (isWatching || isClaimInFlight) return;\n if (!configuration.content.watch) return;\n\n // Spawned by `intlayer watch --with`: the CLI watcher covers the project, and\n // this is known from the environment alone — no need to touch the lock.\n const cliWatcherLabel = getCliContentWatcherLabel();\n\n if (cliWatcherLabel) {\n reportRedundantCliWatcher(configuration, cliWatcherLabel);\n return;\n }\n\n isClaimInFlight = true;\n\n claimAndWatch(configuration).finally(() => {\n isClaimInFlight = false;\n });\n};\n"],"mappings":";;;;;AAUA,MAAM,gBAAgB;;;;;AAMtB,MAAM,4BAA4B;;AAGlC,IAAI,aAAa;;;;;;;;AASjB,IAAI,kBAAkB;;AAGtB,MAAM,6BACJ,eACA,oBAEA,8BAA8B,eAAe;CAC3C,UAAU;CACV,cAAc;AAChB,CAAC;AAEH,MAAM,gBAAgB,OAAO,kBAAiD;CAC5E,MAAM,YAAY,aAAa,aAAa;CAO5C,IAAI,CAAC,MALyB,0BAA0B,eAAe;EACrE,QAAQ;EACR,OAAO;CACT,CAAC,GAEqB;EAGpB,MAAM,QAAQ,MAAM,uBAAuB,aAAa;EAExD,IAAI,OAAO,WAAW,OAAO;GAC3B,0BAA0B,eAAe,MAAM,KAAK;GACpD;EACF;EAUA,AANmB,iBAAiB;GAClC,oBAAoB,aAAa;EACnC,GAAG,yBAIM,CAAC,CAAC,QAAQ;EAEnB;CACF;CAEA,aAAa;CAEb,IAAI;EAGF,MAAM,EAAE,UAAU,MAAM,OAAO;EAE/B,MAAM,MAAM,EAAE,cAAc,CAAC;CAC/B,SAAS,OAAO;EACd,aAAa;EAEb,UAAU,CAAC,kDAAkD,KAAK,GAAG,EACnE,OAAO,QACT,CAAC;CACH;AACF;;;;;;;;;;;;;;AAeA,MAAa,uBAAuB,kBAAwC;CAC1E,IAAI,cAAc,iBAAiB;CACnC,IAAI,CAAC,cAAc,QAAQ,OAAO;CAIlC,MAAM,kBAAkB,0BAA0B;CAElD,IAAI,iBAAiB;EACnB,0BAA0B,eAAe,eAAe;EACxD;CACF;CAEA,kBAAkB;CAElB,cAAc,aAAa,CAAC,CAAC,cAAc;EACzC,kBAAkB;CACpB,CAAC;AACH"}
@@ -1,18 +0,0 @@
1
- import { IntlayerConfig } from "@intlayer/types/config";
2
- //#region src/startContentWatcher.d.ts
3
- /**
4
- * Starts the content declaration watcher for a Vite dev server.
5
- *
6
- * Takes the project's content watcher lock first, so only one process rebuilds
7
- * the dictionaries: it stands down — with an explanation — when `intlayer watch`
8
- * is already running, and lets other tools (e.g. the VS Code extension) see that
9
- * a watcher covers the project.
10
- *
11
- * Deliberately not awaited by the caller: the dev server must not wait on the
12
- * watcher to start serving.
13
- *
14
- * @param configuration - The resolved Intlayer configuration.
15
- */
16
- export declare const startContentWatcher: (configuration: IntlayerConfig) => void;
17
- //#endregion
18
- //# sourceMappingURL=startContentWatcher.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"startContentWatcher.d.ts","names":[],"sources":["../../src/startContentWatcher.ts"],"mappings":";;;;;;;;;;;;;;;qBAqGa,sBAAmB,eAAmB"}