vite-intlayer 8.12.1 → 8.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"IntlayerCompilerPlugin.mjs","names":[],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport {\n type CompilerMode,\n detectPackageName,\n type ExtractPluginOptions,\n type ExtractResult,\n extractContent,\n getExtractPluginOptions,\n writeContentHelper,\n} from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { CompilerConfig, IntlayerConfig } from '@intlayer/types/config';\nimport type { HmrContext, PluginOption } from 'vite';\n\n/**\n * Options for initializing the compiler\n */\nexport type IntlayerCompilerOptions = {\n /**\n * Configuration options for getting the intlayer configuration\n */\n configOptions?: GetConfigurationOptions;\n\n /**\n * Custom compiler configuration to override defaults\n */\n compilerConfig?: Partial<CompilerConfig>;\n};\n\n/**\n * Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer\n *\n * This autonomous compiler handles:\n * - Configuration loading and management\n * - Hot Module Replacement (HMR) for content changes\n * - File transformation with content extraction\n * - Dictionary persistence and building\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { intlayerCompiler } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerCompiler()],\n * });\n * ```\n */\nexport const intlayerCompiler = (\n options?: IntlayerCompilerOptions\n): PluginOption => {\n let config: IntlayerConfig;\n let compilerConfig: ExtractPluginOptions;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n // Track recently processed files to prevent infinite loops\n // Key: file path, Value: timestamp of last processing\n const recentlyProcessedFiles = new Map<string, number>();\n // Track recently written dictionaries to prevent duplicate writes\n // Key: dictionary key, Value: hash of content that was written\n const recentDictionaryContent = new Map<string, string>();\n // Debounce window in milliseconds - skip re-processing files within this window\n const DEBOUNCE_MS = 500;\n\n /**\n * Check if a file was recently processed (within debounce window)\n * and should be skipped to prevent infinite loops\n */\n const wasRecentlyProcessed = (filePath: string): boolean => {\n const lastProcessed = recentlyProcessedFiles.get(filePath);\n if (!lastProcessed) return false;\n\n const now = Date.now();\n return now - lastProcessed < DEBOUNCE_MS;\n };\n\n /**\n * Mark a file as recently processed\n */\n const markAsProcessed = (filePath: string): void => {\n recentlyProcessedFiles.set(filePath, Date.now());\n\n // Clean up old entries to prevent memory leaks\n const now = Date.now();\n for (const [path, timestamp] of recentlyProcessedFiles.entries()) {\n if (now - timestamp > DEBOUNCE_MS * 2) {\n recentlyProcessedFiles.delete(path);\n }\n }\n };\n\n /**\n * Create a simple hash of content for comparison\n * Used to detect if dictionary content has actually changed\n */\n const hashContent = (content: Record<string, string>): string =>\n JSON.stringify(\n Object.keys(content)\n .sort()\n .map((key) => [key, content[key]])\n );\n\n /**\n * Check if dictionary content has changed since last write\n */\n const hasDictionaryContentChanged = (\n dictionaryKey: string,\n content: Record<string, string>\n ): boolean => {\n const newHash = hashContent(content);\n const previousHash = recentDictionaryContent.get(dictionaryKey);\n\n if (previousHash === newHash) {\n return false;\n }\n\n // Update the stored hash\n recentDictionaryContent.set(dictionaryKey, newHash);\n return true;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n filesList = compilerConfig.filesList;\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(options?.configOptions);\n\n compilerConfig = getExtractPluginOptions(config, compilerMode);\n\n logger = getAppLogger(config);\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: configResolved\n * Called when Vite config is resolved\n */\n const configResolved = async (viteConfig: {\n env?: { DEV?: boolean };\n root: string;\n }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\n\n await init(compilerMode);\n };\n\n /**\n * Build start hook - no longer needs to prepare dictionaries\n * The compiler is now autonomous and extracts content inline\n */\n const buildStart = async (): Promise<void> => {\n // Bootstrap dictionaries and types before build starts\n // This ensures existing dictionaries are available for resolution\n try {\n logger('Intlayer compiler initialized', {\n level: 'info',\n });\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to prepare Intlayer: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Build end hook - wait for any pending dictionary writes\n */\n const buildEnd = async (): Promise<void> => {\n // Wait for any pending dictionary writes to complete\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async ({\n file,\n server,\n modules,\n }: HmrContext): Promise<void> => {\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((fileEl) => fileEl === file);\n\n if (isTransformableFile) {\n // Check if this file was recently processed to prevent infinite loops\n // When a component is transformed, it writes a dictionary, which triggers HMR,\n // which would re-transform the component - this debounce prevents that loop\n if (wasRecentlyProcessed(file)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return undefined;\n }\n\n // Mark file as being processed before transformation\n markAsProcessed(file);\n\n // Invalidate all affected modules to ensure re-transform\n for (const mod of modules) {\n server.moduleGraph.invalidateModule(mod);\n }\n\n // Force re-transform by reading and processing the file\n // This ensures content extraction happens on every file change\n try {\n const code = await readFile(file, 'utf-8');\n\n // Trigger the transform manually to extract content\n await transformHandler(code, file);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n // Trigger full reload for content changes\n server.ws.send({ type: 'full-reload' });\n }\n };\n\n /**\n * Write and build one or more dictionaries based on extracted content.\n * Leverages shared logic from @intlayer/babel.\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content, filePath: sourceFilePath } = result;\n\n // Skip if content hasn't changed - prevents infinite loops during HMR\n if (!hasDictionaryContentChanged(dictionaryKey, content)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return;\n }\n\n try {\n await writeContentHelper(content, dictionaryKey, sourceFilePath!, config);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Callback for when content is extracted from a file\n * Immediately writes and builds the dictionary\n */\n const handleExtractedContent = async (\n result: ExtractResult\n ): Promise<void> => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${colorizeNumber(contentKeys.length)} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`,\n {\n level: 'info',\n }\n );\n\n // Chain the write operation to ensure sequential writes\n pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve())\n .then(() => writeAndBuildDictionary(result))\n .catch((error) => {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`,\n {\n level: 'error',\n }\n );\n });\n\n return pendingDictionaryWrite;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type.\n * Delegates to `extractContent` from `@intlayer/babel` which handles\n * JS/TS/JSX/TSX/Vue/Svelte/Astro extraction and transformation.\n */\n const transformHandler = async (code: string, id: string) => {\n // Only transform if compiler is enabled\n if (!compilerConfig.enabled) {\n return undefined;\n }\n\n // Skip virtual modules (query strings indicate compiled/virtual modules)\n // e.g., App.svelte?svelte&type=style, Component.vue?vue&type=script\n if (id.includes('?')) {\n return undefined;\n }\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n\n try {\n const packageName = detectPackageName(dirname(filename));\n\n const result = await extractContent(filename, packageName, {\n configuration: config,\n code,\n // Dictionary writing is handled by handleExtractedContent below.\n onExtract: async ({ key, content }) => {\n await handleExtractedContent({\n dictionaryKey: key,\n content,\n filePath: filename,\n locale: config.internationalization.defaultLocale,\n });\n },\n });\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before any subsequent processing\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.transformedCode) {\n return {\n code: result.transformedCode,\n };\n }\n } catch (error) {\n logger(\n [\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}:`,\n error,\n ],\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n configResolved,\n buildStart,\n buildEnd,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig, env) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(options?.configOptions);\n }\n if (!logger) {\n logger = getAppLogger(config);\n }\n\n if (!config.compiler.output) {\n logger(\n `${x} No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`,\n {\n level: 'error',\n }\n );\n\n return false;\n }\n\n if (!compilerConfig) {\n compilerConfig = getExtractPluginOptions(config, env.command);\n }\n\n return compilerConfig.enabled;\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"IntlayerCompilerPlugin.mjs","names":[],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport {\n type CompilerMode,\n detectPackageName,\n type ExtractPluginOptions,\n type ExtractResult,\n extractContent,\n getExtractPluginOptions,\n writeContentHelper,\n} from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { CompilerConfig, IntlayerConfig } from '@intlayer/types/config';\nimport type { HmrContext, PluginOption } from 'vite';\n\n/**\n * Options for initializing the compiler\n */\nexport type IntlayerCompilerOptions = {\n /**\n * Configuration options for getting the intlayer configuration\n */\n configOptions?: GetConfigurationOptions;\n\n /**\n * Custom compiler configuration to override defaults\n */\n compilerConfig?: Partial<CompilerConfig>;\n};\n\n/**\n * Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer\n *\n * This autonomous compiler handles:\n * - Configuration loading and management\n * - Hot Module Replacement (HMR) for content changes\n * - File transformation with content extraction\n * - Dictionary persistence and building\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { intlayerCompiler } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerCompiler()],\n * });\n * ```\n */\nexport const intlayerCompiler = (\n options?: IntlayerCompilerOptions\n): PluginOption => {\n let config: IntlayerConfig;\n let compilerConfig: ExtractPluginOptions;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n // Track recently processed files to prevent infinite loops\n // Key: file path, Value: timestamp of last processing\n const recentlyProcessedFiles = new Map<string, number>();\n // Track recently written dictionaries to prevent duplicate writes\n // Key: dictionary key, Value: hash of content that was written\n const recentDictionaryContent = new Map<string, string>();\n // Debounce window in milliseconds - skip re-processing files within this window\n const DEBOUNCE_MS = 500;\n\n /**\n * Check if a file was recently processed (within debounce window)\n * and should be skipped to prevent infinite loops\n */\n const wasRecentlyProcessed = (filePath: string): boolean => {\n const lastProcessed = recentlyProcessedFiles.get(filePath);\n if (!lastProcessed) return false;\n\n const now = Date.now();\n return now - lastProcessed < DEBOUNCE_MS;\n };\n\n /**\n * Mark a file as recently processed\n */\n const markAsProcessed = (filePath: string): void => {\n recentlyProcessedFiles.set(filePath, Date.now());\n\n // Clean up old entries to prevent memory leaks\n const now = Date.now();\n for (const [path, timestamp] of recentlyProcessedFiles.entries()) {\n if (now - timestamp > DEBOUNCE_MS * 2) {\n recentlyProcessedFiles.delete(path);\n }\n }\n };\n\n /**\n * Create a simple hash of content for comparison\n * Used to detect if dictionary content has actually changed\n */\n const hashContent = (content: Record<string, string>): string =>\n JSON.stringify(\n Object.keys(content)\n .sort()\n .map((key) => [key, content[key]])\n );\n\n /**\n * Check if dictionary content has changed since last write\n */\n const hasDictionaryContentChanged = (\n dictionaryKey: string,\n content: Record<string, string>\n ): boolean => {\n const newHash = hashContent(content);\n const previousHash = recentDictionaryContent.get(dictionaryKey);\n\n if (previousHash === newHash) {\n return false;\n }\n\n // Update the stored hash\n recentDictionaryContent.set(dictionaryKey, newHash);\n return true;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n filesList = compilerConfig.filesList;\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(options?.configOptions);\n\n compilerConfig = getExtractPluginOptions(config, compilerMode);\n\n logger = getAppLogger(config);\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: configResolved\n * Called when Vite config is resolved\n */\n const configResolved = async (viteConfig: {\n env?: { DEV?: boolean };\n root: string;\n }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\n\n await init(compilerMode);\n };\n\n /**\n * Build start hook - no longer needs to prepare dictionaries\n * The compiler is now autonomous and extracts content inline\n */\n const buildStart = async (): Promise<void> => {\n // Bootstrap dictionaries and types before build starts\n // This ensures existing dictionaries are available for resolution\n try {\n logger('Intlayer compiler initialized', {\n level: 'info',\n });\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to prepare Intlayer: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Build end hook - wait for any pending dictionary writes\n */\n const buildEnd = async (): Promise<void> => {\n // Wait for any pending dictionary writes to complete\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async ({\n file,\n server,\n modules,\n }: HmrContext): Promise<void> => {\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((fileEl) => fileEl === file);\n\n if (isTransformableFile) {\n // Check if this file was recently processed to prevent infinite loops\n // When a component is transformed, it writes a dictionary, which triggers HMR,\n // which would re-transform the component - this debounce prevents that loop\n if (wasRecentlyProcessed(file)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return undefined;\n }\n\n // Mark file as being processed before transformation\n markAsProcessed(file);\n\n // Invalidate all affected modules to ensure re-transform\n for (const mod of modules) {\n server.moduleGraph.invalidateModule(mod);\n }\n\n // Force re-transform by reading and processing the file\n // This ensures content extraction happens on every file change\n try {\n const code = await readFile(file, 'utf-8');\n\n // Trigger the transform manually to extract content\n await transformHandler(code, file);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n // Trigger full reload for content changes\n server.ws.send({ type: 'full-reload' });\n }\n };\n\n /**\n * Write and build one or more dictionaries based on extracted content.\n * Leverages shared logic from @intlayer/babel.\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content, filePath: sourceFilePath } = result;\n\n // Skip if content hasn't changed - prevents infinite loops during HMR\n if (!hasDictionaryContentChanged(dictionaryKey, content)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return;\n }\n\n try {\n await writeContentHelper(content, dictionaryKey, sourceFilePath!, config);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Callback for when content is extracted from a file\n * Immediately writes and builds the dictionary\n */\n const handleExtractedContent = async (\n result: ExtractResult\n ): Promise<void> => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${colorizeNumber(contentKeys.length)} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`,\n {\n level: 'info',\n }\n );\n\n // Chain the write operation to ensure sequential writes\n pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve())\n .then(() => writeAndBuildDictionary(result))\n .catch((error) => {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`,\n {\n level: 'error',\n }\n );\n });\n\n return pendingDictionaryWrite;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type.\n * Delegates to `extractContent` from `@intlayer/babel` which handles\n * JS/TS/JSX/TSX/Vue/Svelte/Astro extraction and transformation.\n */\n const transformHandler = async (code: string, id: string) => {\n // Only transform if compiler is enabled\n if (!compilerConfig.enabled) {\n return undefined;\n }\n\n // Skip virtual modules (query strings indicate compiled/virtual modules)\n // e.g., App.svelte?svelte&type=style, Component.vue?vue&type=script\n if (id.includes('?')) {\n return undefined;\n }\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n\n try {\n const packageName = detectPackageName(dirname(filename));\n\n const result = await extractContent(filename, packageName, {\n configuration: config,\n code,\n // Dictionary writing is handled by handleExtractedContent below.\n onExtract: async ({ key, content }) => {\n await handleExtractedContent({\n dictionaryKey: key,\n content,\n filePath: filename,\n locale: config.internationalization.defaultLocale,\n });\n },\n });\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before any subsequent processing\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.transformedCode) {\n return {\n code: result.transformedCode,\n };\n }\n } catch (error) {\n logger(\n [\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}:`,\n error,\n ],\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n configResolved,\n buildStart,\n buildEnd,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig, env) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(options?.configOptions);\n }\n if (!logger) {\n logger = getAppLogger(config);\n }\n\n if (!config.compiler.output) {\n logger(\n `${x} No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`,\n {\n level: 'error',\n }\n );\n\n return false;\n }\n\n if (!compilerConfig) {\n compilerConfig = getExtractPluginOptions(config, env.command);\n }\n\n return compilerConfig.enabled;\n },\n };\n};\n"],"mappings":"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"}
@@ -3,6 +3,6 @@ import { intlayerMinify } from "./intlayerMinifyPlugin.mjs";
3
3
  import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
4
4
  import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
5
5
  import { intLayerPlugin, intlayer, intlayerPlugin } from "./intlayerPlugin.mjs";
6
- import { intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.mjs";
6
+ import { createIntlayerProxyHandler, intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.mjs";
7
7
 
8
- export { intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerProxy, intlayerPrune };
8
+ export { createIntlayerProxyHandler, intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerProxy, intlayerPrune };
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerMinifyPlugin.mjs","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { NestedRenameMap, PruneContext } from '@intlayer/babel';\nimport { formatPath, runOnce } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\n// Field-rename helper\n\n/**\n * Recursively renames user-defined keys in a compiled intlayer content value\n * using the provided `NestedRenameMap`.\n *\n * Traversal rules (mirrors `buildNestedRenameMapFromContent`):\n * - Arrays → each element is recursed into with the same rename map.\n * This mirrors the array pass-through in the source-code rename walk,\n * where numeric indices (e.g. [0]) are transparent.\n * - Object with `nodeType: 'translation'` → intlayer translation node.\n * Keys at this level (`nodeType`, `translation`) are NOT renamed.\n * Recurse into each per-locale value with the same rename map.\n * - Object without `nodeType` → user-defined record.\n * Rename its keys using the current rename map level, then recurse into\n * each value with that entry's `children` map.\n * - Primitives → returned as-is.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n // Arrays: each element is renamed with the same map (indices are transparent).\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\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 with the same rename 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; // key not in map – keep as-is (e.g. already-pruned)\n }\n }\n return result;\n};\n\n/**\n * Applies the nested field rename map to a parsed dictionary object and\n * returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)\n * are never touched; only keys inside `content` are renamed.\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\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// Plugin\n\n/**\n * Returns the Vite plugin that minifies compiled dictionary JSON files by\n * removing all unnecessary whitespace and optionally renaming content field\n * names to short alphabetic aliases (a, b, c, …).\n *\n * Targets:\n * - `<dictionariesDir>/**\\/*.json` – static all-locale dictionaries\n * - `<dynamicDictionariesDir>/**\\/*.json` – per-locale dynamic dictionaries\n * - `<fetchDictionariesDir>/**\\/*.json` – per-locale fetch dictionaries\n *\n * The plugin is deliberately independent of the prune plugin: it can run\n * on its own when only `build.minify` is enabled. When both `purge` and\n * `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`\n * and is registered before this one); this plugin then receives the already-\n * pruned JSON, renames its field keys, and compacts it.\n *\n * Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:\n * those dictionaries encountered a structural issue during the prune phase\n * and should be left completely untouched to avoid shipping broken data.\n *\n * Field renaming (property mangling) is applied only for dictionaries that\n * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.\n * The corresponding rename is also applied to source-file property accesses by\n * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields\n * such as `nodeType` are never renamed.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Optional shared state from the prune plugin. When\n * provided, dictionaries flagged as edge-cases are\n * skipped and field renames are applied. Pass `null`\n * if the prune plugin is not active.\n */\nexport const intlayerMinify = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const { dictionariesDir, dynamicDictionariesDir, baseDir } =\n intlayerConfig.system;\n\n // Fetch-mode dictionaries are served from a remote API at runtime using their\n // original field names. Minifying them (renaming fields) would create a\n // mismatch between the server response and the renamed client-side accesses.\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir));\n\n const isMinifyEnabled = (\n _config: unknown,\n env: { command: string }\n ): boolean => {\n const isBuildCommand = env.command === 'build';\n const isOptimizeActive =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isOptimizeActive) return false;\n if (!minify) return false;\n if (!isBuildCommand) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary minification is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n '— the editor requires full dictionary content.',\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n return false;\n }\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-plugin-enabled.lock'\n ),\n () =>\n logger([\n 'Dictionary minification',\n colorize('enabled', ANSIColors.GREEN),\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n };\n\n const minifyPlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-minify',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM\n // conversion. Declaration order in the plugin array ensures this runs\n // after the prune plugin (which is also 'pre' but registered earlier).\n enforce: 'pre',\n apply: isMinifyEnabled,\n\n transform: (rawJsonCode, moduleId) => {\n const absoluteFilePath = moduleId.split('?', 1)[0];\n\n if (!isDictionaryJsonFile(absoluteFilePath)) return null;\n\n // Parse JSON\n let parsedDict: Record<string, unknown>;\n try {\n parsedDict = JSON.parse(rawJsonCode) as Record<string, unknown>;\n } catch (parseError) {\n logger(\n [\n `Could not parse`,\n formatPath(absoluteFilePath),\n `as JSON. Skipping minification for this file.`,\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n const dictionaryKey =\n typeof parsedDict.key === 'string' ? parsedDict.key : undefined;\n\n // Skip edge-case dictionaries\n if (\n pruneContext &&\n dictionaryKey &&\n pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)\n ) {\n return null; // structural issue flagged during prune – leave untouched\n }\n\n // Apply field rename (property mangling)\n if (pruneContext && dictionaryKey) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);\n }\n }\n\n // Strip all top-level metadata – ship only key + content\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n content: parsedDict.content,\n }),\n map: null,\n };\n },\n };\n\n return [minifyPlugin];\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerMinifyPlugin.mjs","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { NestedRenameMap, PruneContext } from '@intlayer/babel';\nimport { formatPath, runOnce } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\n// Field-rename helper\n\n/**\n * Recursively renames user-defined keys in a compiled intlayer content value\n * using the provided `NestedRenameMap`.\n *\n * Traversal rules (mirrors `buildNestedRenameMapFromContent`):\n * - Arrays → each element is recursed into with the same rename map.\n * This mirrors the array pass-through in the source-code rename walk,\n * where numeric indices (e.g. [0]) are transparent.\n * - Object with `nodeType: 'translation'` → intlayer translation node.\n * Keys at this level (`nodeType`, `translation`) are NOT renamed.\n * Recurse into each per-locale value with the same rename map.\n * - Object without `nodeType` → user-defined record.\n * Rename its keys using the current rename map level, then recurse into\n * each value with that entry's `children` map.\n * - Primitives → returned as-is.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n // Arrays: each element is renamed with the same map (indices are transparent).\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\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 with the same rename 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; // key not in map – keep as-is (e.g. already-pruned)\n }\n }\n return result;\n};\n\n/**\n * Applies the nested field rename map to a parsed dictionary object and\n * returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)\n * are never touched; only keys inside `content` are renamed.\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\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// Plugin\n\n/**\n * Returns the Vite plugin that minifies compiled dictionary JSON files by\n * removing all unnecessary whitespace and optionally renaming content field\n * names to short alphabetic aliases (a, b, c, …).\n *\n * Targets:\n * - `<dictionariesDir>/**\\/*.json` – static all-locale dictionaries\n * - `<dynamicDictionariesDir>/**\\/*.json` – per-locale dynamic dictionaries\n * - `<fetchDictionariesDir>/**\\/*.json` – per-locale fetch dictionaries\n *\n * The plugin is deliberately independent of the prune plugin: it can run\n * on its own when only `build.minify` is enabled. When both `purge` and\n * `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`\n * and is registered before this one); this plugin then receives the already-\n * pruned JSON, renames its field keys, and compacts it.\n *\n * Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:\n * those dictionaries encountered a structural issue during the prune phase\n * and should be left completely untouched to avoid shipping broken data.\n *\n * Field renaming (property mangling) is applied only for dictionaries that\n * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.\n * The corresponding rename is also applied to source-file property accesses by\n * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields\n * such as `nodeType` are never renamed.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Optional shared state from the prune plugin. When\n * provided, dictionaries flagged as edge-cases are\n * skipped and field renames are applied. Pass `null`\n * if the prune plugin is not active.\n */\nexport const intlayerMinify = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const { dictionariesDir, dynamicDictionariesDir, baseDir } =\n intlayerConfig.system;\n\n // Fetch-mode dictionaries are served from a remote API at runtime using their\n // original field names. Minifying them (renaming fields) would create a\n // mismatch between the server response and the renamed client-side accesses.\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir));\n\n const isMinifyEnabled = (\n _config: unknown,\n env: { command: string }\n ): boolean => {\n const isBuildCommand = env.command === 'build';\n const isOptimizeActive =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isOptimizeActive) return false;\n if (!minify) return false;\n if (!isBuildCommand) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary minification is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n '— the editor requires full dictionary content.',\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n return false;\n }\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-plugin-enabled.lock'\n ),\n () =>\n logger([\n 'Dictionary minification',\n colorize('enabled', ANSIColors.GREEN),\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n };\n\n const minifyPlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-minify',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM\n // conversion. Declaration order in the plugin array ensures this runs\n // after the prune plugin (which is also 'pre' but registered earlier).\n enforce: 'pre',\n apply: isMinifyEnabled,\n\n transform: (rawJsonCode, moduleId) => {\n const absoluteFilePath = moduleId.split('?', 1)[0];\n\n if (!isDictionaryJsonFile(absoluteFilePath)) return null;\n\n // Parse JSON\n let parsedDict: Record<string, unknown>;\n try {\n parsedDict = JSON.parse(rawJsonCode) as Record<string, unknown>;\n } catch (parseError) {\n logger(\n [\n `Could not parse`,\n formatPath(absoluteFilePath),\n `as JSON. Skipping minification for this file.`,\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n const dictionaryKey =\n typeof parsedDict.key === 'string' ? parsedDict.key : undefined;\n\n // Skip edge-case dictionaries\n if (\n pruneContext &&\n dictionaryKey &&\n pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)\n ) {\n return null; // structural issue flagged during prune – leave untouched\n }\n\n // Apply field rename (property mangling)\n if (pruneContext && dictionaryKey) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);\n }\n }\n\n // Strip all top-level metadata – ship only key + content\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n content: parsedDict.content,\n }),\n map: null,\n };\n },\n };\n\n return [minifyPlugin];\n};\n"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { createIntlayerProxyHandler } from "./intlayerProxyPlugin.mjs";
2
+ import * as ANSIColors from "@intlayer/config/colors";
3
+ import { colorize, getAppLogger } from "@intlayer/config/logger";
4
+ import { getConfiguration } from "@intlayer/config/node";
5
+
6
+ //#region src/intlayerNitroHandler.ts
7
+ getAppLogger(getConfiguration())(`Intlayer proxy ${colorize("enabled", ANSIColors.GREEN)}`, { level: "info" });
8
+ const nodeMiddleware = createIntlayerProxyHandler();
9
+ /**
10
+ * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).
11
+ *
12
+ * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event
13
+ * model exclusively and is therefore compatible with ALL Nitro presets — including Bun
14
+ * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with
15
+ * "undefined is not an object (evaluating 'event.node.req')".
16
+ *
17
+ * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware
18
+ * via lightweight IncomingMessage / ServerResponse shims:
19
+ *
20
+ * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends
21
+ * the correct HTTP response to the browser.
22
+ * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten
23
+ * URL so `event.path` (a getter) returns the new pathname for downstream handlers and
24
+ * the Nitro router.
25
+ * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to
26
+ * the next handler / route.
27
+ */
28
+ var intlayerNitroHandler_default = async (event) => new Promise((resolve) => {
29
+ const initialPath = event.path;
30
+ /**
31
+ * Minimal IncomingMessage shim.
32
+ *
33
+ * Only the fields actually read by createIntlayerProxyHandler are populated:
34
+ * - url : the current pathname + search, modified for rewrites
35
+ * - headers.cookie : locale cookie detection
36
+ * - headers.host : domain-based locale routing
37
+ * - headers.accept-language : browser Accept-Language fallback
38
+ * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups
39
+ *
40
+ * headers must be a mutable plain object because setLocaleInStorageServer
41
+ * writes Set-Cookie back via req.headers[name] = value.
42
+ */
43
+ const fakeReq = {
44
+ url: initialPath,
45
+ method: "GET",
46
+ headers: {
47
+ cookie: event.headers.get("cookie") ?? "",
48
+ host: event.headers.get("host") ?? "",
49
+ "accept-language": event.headers.get("accept-language") ?? "",
50
+ "x-forwarded-host": event.headers.get("x-forwarded-host") ?? "",
51
+ "x-forwarded-proto": event.headers.get("x-forwarded-proto") ?? ""
52
+ }
53
+ };
54
+ let responseStatusCode = 200;
55
+ const accumulatedHeaders = {};
56
+ /**
57
+ * Minimal ServerResponse shim.
58
+ *
59
+ * Implements only the methods that createIntlayerProxyHandler invokes:
60
+ * writeHead() — status + Location header for 301 redirects
61
+ * setHeader() — Set-Cookie written by setLocaleInStorageServer
62
+ * getHeader() — defensive read-back (not strictly required but safe)
63
+ * end() — finalises the response; for redirects this returns a
64
+ * Web API Response object that Nitro sends to the client
65
+ */
66
+ const fakeRes = {
67
+ writeHead(statusCode, headersArg) {
68
+ responseStatusCode = statusCode;
69
+ if (headersArg && typeof headersArg === "object") for (const [key, value] of Object.entries(headersArg)) accumulatedHeaders[key.toLowerCase()] = Array.isArray(value) ? value[0] ?? "" : String(value);
70
+ return fakeRes;
71
+ },
72
+ setHeader(name, value) {
73
+ accumulatedHeaders[name.toLowerCase()] = Array.isArray(value) ? value[0] ?? "" : String(value);
74
+ return fakeRes;
75
+ },
76
+ getHeader(name) {
77
+ return accumulatedHeaders[name.toLowerCase()];
78
+ },
79
+ getHeaders() {
80
+ return { ...accumulatedHeaders };
81
+ },
82
+ end(body) {
83
+ const webHeaders = new Headers();
84
+ for (const [key, value] of Object.entries(accumulatedHeaders)) webHeaders.set(key, value);
85
+ resolve(new Response(responseStatusCode >= 300 && responseStatusCode < 400 ? null : typeof body === "string" ? body : null, {
86
+ status: responseStatusCode,
87
+ headers: webHeaders
88
+ }));
89
+ return fakeRes;
90
+ },
91
+ headersSent: false
92
+ };
93
+ nodeMiddleware(fakeReq, fakeRes, () => {
94
+ const rewrittenPath = fakeReq.url;
95
+ if (rewrittenPath !== initialPath) try {
96
+ event.url = new URL(rewrittenPath, event.url.origin);
97
+ } catch {
98
+ console.error("[intlayer-proxy] URL rewrite failed — invalid path:", rewrittenPath);
99
+ }
100
+ if (Object.keys(accumulatedHeaders).length > 0) for (const [key, value] of Object.entries(accumulatedHeaders)) event.res.headers.set(key, value);
101
+ resolve(void 0);
102
+ });
103
+ });
104
+
105
+ //#endregion
106
+ export { intlayerNitroHandler_default as default };
107
+ //# sourceMappingURL=intlayerNitroHandler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerNitroHandler.mjs","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { createIntlayerProxyHandler } from './intlayerProxyPlugin';\n\n/**\n * Minimal duck-type for h3 v2's H3Event.\n *\n * We intentionally avoid importing from 'h3' to keep this file runtime-agnostic —\n * Nitro bundles h3 internally and provides the populated event at runtime. Using a\n * structural type here means the file compiles without h3 in devDependencies and\n * works with any h3 v2-compatible runtime (Bun, Deno, Node).\n */\ntype H3EventLike = {\n /**\n * pathname + search — a computed getter on H3Event:\n * `return this.url.pathname + this.url.search`\n */\n readonly path: string;\n /**\n * Full URL object — a **plain property** (not a getter) on H3Event, safe to\n * replace for internal URL rewrites. After assignment, `event.path` will\n * automatically reflect the new pathname + search via the getter.\n */\n url: URL;\n /**\n * Web Fetch API Headers — always populated in h3 v2 regardless of preset\n * (Node, Bun, Deno). Use `.get(name)` instead of bracket-access.\n */\n readonly headers: Headers;\n /**\n * Lazy response object — created on first access; its `headers` carry outgoing\n * response headers (e.g. Set-Cookie) that h3 merges into the HTTP response.\n */\n readonly res: {\n readonly headers: Headers;\n };\n};\n\nconst intlayerConfig = getConfiguration();\nconst logger = getAppLogger(intlayerConfig);\nlogger(`Intlayer proxy ${colorize('enabled', ANSIColors.GREEN)}`, {\n level: 'info',\n});\n\nconst nodeMiddleware = createIntlayerProxyHandler();\n\n/**\n * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).\n *\n * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event\n * model exclusively and is therefore compatible with ALL Nitro presets — including Bun\n * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with\n * \"undefined is not an object (evaluating 'event.node.req')\".\n *\n * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware\n * via lightweight IncomingMessage / ServerResponse shims:\n *\n * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends\n * the correct HTTP response to the browser.\n * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten\n * URL so `event.path` (a getter) returns the new pathname for downstream handlers and\n * the Nitro router.\n * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to\n * the next handler / route.\n */\nexport default async (event: H3EventLike): Promise<Response | void> =>\n new Promise<Response | void>((resolve) => {\n const initialPath = event.path;\n\n /**\n * Minimal IncomingMessage shim.\n *\n * Only the fields actually read by createIntlayerProxyHandler are populated:\n * - url : the current pathname + search, modified for rewrites\n * - headers.cookie : locale cookie detection\n * - headers.host : domain-based locale routing\n * - headers.accept-language : browser Accept-Language fallback\n * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups\n *\n * headers must be a mutable plain object because setLocaleInStorageServer\n * writes Set-Cookie back via req.headers[name] = value.\n */\n const fakeReq = {\n url: initialPath,\n method: 'GET',\n headers: {\n cookie: event.headers.get('cookie') ?? '',\n host: event.headers.get('host') ?? '',\n 'accept-language': event.headers.get('accept-language') ?? '',\n 'x-forwarded-host': event.headers.get('x-forwarded-host') ?? '',\n 'x-forwarded-proto': event.headers.get('x-forwarded-proto') ?? '',\n } as Record<string, string>,\n } as unknown as IncomingMessage;\n\n let responseStatusCode = 200;\n const accumulatedHeaders: Record<string, string> = {};\n\n /**\n * Minimal ServerResponse shim.\n *\n * Implements only the methods that createIntlayerProxyHandler invokes:\n * writeHead() — status + Location header for 301 redirects\n * setHeader() — Set-Cookie written by setLocaleInStorageServer\n * getHeader() — defensive read-back (not strictly required but safe)\n * end() — finalises the response; for redirects this returns a\n * Web API Response object that Nitro sends to the client\n */\n const fakeRes = {\n writeHead(\n statusCode: number,\n headersArg?: Record<string, string | string[] | number> | string\n ) {\n // Capture the status code and any headers supplied alongside writeHead.\n responseStatusCode = statusCode;\n if (headersArg && typeof headersArg === 'object') {\n for (const [key, value] of Object.entries(headersArg)) {\n accumulatedHeaders[key.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n }\n }\n return fakeRes;\n },\n setHeader(name: string, value: string | number | string[]) {\n // Capture Set-Cookie and other outgoing headers.\n accumulatedHeaders[name.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n return fakeRes;\n },\n getHeader(name: string) {\n return accumulatedHeaders[name.toLowerCase()];\n },\n getHeaders() {\n return { ...accumulatedHeaders };\n },\n end(body?: string | Buffer | null) {\n // Build a Web API Response from accumulated status + headers + body.\n // For 3xx redirects the body is intentionally null.\n const webHeaders = new Headers();\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n webHeaders.set(key, value);\n }\n const isRedirect =\n responseStatusCode >= 300 && responseStatusCode < 400;\n resolve(\n new Response(\n isRedirect ? null : typeof body === 'string' ? body : null,\n {\n status: responseStatusCode,\n headers: webHeaders,\n }\n )\n );\n return fakeRes;\n },\n headersSent: false,\n } as unknown as ServerResponse<IncomingMessage>;\n\n nodeMiddleware(fakeReq, fakeRes, () => {\n // Middleware called next() — either a URL rewrite or a true pass-through.\n const rewrittenPath = fakeReq.url as string;\n\n if (rewrittenPath !== initialPath) {\n // The middleware rewrote the URL (e.g. /about → /en/about for locale prefix).\n // Replace event.url so that event.path (the getter: url.pathname + url.search)\n // returns the new path and the Nitro router matches the correct route.\n //\n // event.url is a plain property on h3 v2's H3Event (not a getter), so direct\n // assignment is safe. We use event.url.origin as the base so relative paths\n // resolve correctly; for path-only requests origin defaults to http://localhost.\n try {\n event.url = new URL(rewrittenPath, event.url.origin);\n } catch {\n console.error(\n '[intlayer-proxy] URL rewrite failed — invalid path:',\n rewrittenPath\n );\n }\n }\n\n // Forward any Set-Cookie or custom headers set by setLocaleInStorageServer to\n // the h3 v2 response object so they are included in the outgoing HTTP response.\n // Accessing event.res lazily creates the H3EventResponse (no cost if empty).\n if (Object.keys(accumulatedHeaders).length > 0) {\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n event.res.headers.set(key, value);\n }\n }\n\n resolve(undefined);\n });\n });\n"],"mappings":";;;;;;AAyCe,aADQ,kBACmB,CACpC,CAAC,kBAAkB,SAAS,WAAW,WAAW,MAAM,IAAI,EAChE,OAAO,QACR,CAAC;AAEF,MAAM,iBAAiB,4BAA4B;;;;;;;;;;;;;;;;;;;;AAqBnD,mCAAe,OAAO,UACpB,IAAI,SAA0B,YAAY;CACxC,MAAM,cAAc,MAAM;;;;;;;;;;;;;;CAe1B,MAAM,UAAU;EACd,KAAK;EACL,QAAQ;EACR,SAAS;GACP,QAAQ,MAAM,QAAQ,IAAI,SAAS,IAAI;GACvC,MAAM,MAAM,QAAQ,IAAI,OAAO,IAAI;GACnC,mBAAmB,MAAM,QAAQ,IAAI,kBAAkB,IAAI;GAC3D,oBAAoB,MAAM,QAAQ,IAAI,mBAAmB,IAAI;GAC7D,qBAAqB,MAAM,QAAQ,IAAI,oBAAoB,IAAI;GAChE;EACF;CAED,IAAI,qBAAqB;CACzB,MAAM,qBAA6C,EAAE;;;;;;;;;;;CAYrD,MAAM,UAAU;EACd,UACE,YACA,YACA;AAEA,wBAAqB;AACrB,OAAI,cAAc,OAAO,eAAe,SACtC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,CACnD,oBAAmB,IAAI,aAAa,IAAI,MAAM,QAAQ,MAAM,GACvD,MAAM,MAAM,KACb,OAAO,MAAM;AAGrB,UAAO;;EAET,UAAU,MAAc,OAAmC;AAEzD,sBAAmB,KAAK,aAAa,IAAI,MAAM,QAAQ,MAAM,GACxD,MAAM,MAAM,KACb,OAAO,MAAM;AACjB,UAAO;;EAET,UAAU,MAAc;AACtB,UAAO,mBAAmB,KAAK,aAAa;;EAE9C,aAAa;AACX,UAAO,EAAE,GAAG,oBAAoB;;EAElC,IAAI,MAA+B;GAGjC,MAAM,aAAa,IAAI,SAAS;AAChC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,mBAAmB,CAC3D,YAAW,IAAI,KAAK,MAAM;AAI5B,WACE,IAAI,SAFJ,sBAAsB,OAAO,qBAAqB,MAGnC,OAAO,OAAO,SAAS,WAAW,OAAO,MACtD;IACE,QAAQ;IACR,SAAS;IACV,CACF,CACF;AACD,UAAO;;EAET,aAAa;EACd;AAED,gBAAe,SAAS,eAAe;EAErC,MAAM,gBAAgB,QAAQ;AAE9B,MAAI,kBAAkB,YAQpB,KAAI;AACF,SAAM,MAAM,IAAI,IAAI,eAAe,MAAM,IAAI,OAAO;UAC9C;AACN,WAAQ,MACN,uDACA,cACD;;AAOL,MAAI,OAAO,KAAK,mBAAmB,CAAC,SAAS,EAC3C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,mBAAmB,CAC3D,OAAM,IAAI,QAAQ,IAAI,KAAK,MAAM;AAIrC,UAAQ,OAAU;GAClB;EACF"}
@@ -1,7 +1,7 @@
1
1
  import { intlayerVueAsyncPlugin } from "./intlayerVueAsyncPlugin.mjs";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { INTLAYER_USAGE_REGEX, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, optimizeSourceFile, renameFieldsInSourceFile } from "@intlayer/babel";
4
+ import { INTLAYER_OR_COMPAT_USAGE_REGEX, INTLAYER_USAGE_REGEX, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, optimizeSourceFile, renameFieldsInSourceFile } from "@intlayer/babel";
5
5
  import * as ANSIColors from "@intlayer/config/colors";
6
6
  import { colorize, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
7
7
  import { buildComponentFilesList, formatPath, runOnce } from "@intlayer/chokidar/utils";
@@ -74,7 +74,7 @@ const intlayerOptimize = async (intlayerConfig, pruneContext) => {
74
74
  } catch {
75
75
  return;
76
76
  }
77
- if (!INTLAYER_USAGE_REGEX.test(sourceCode)) return;
77
+ if (!INTLAYER_OR_COMPAT_USAGE_REGEX.test(sourceCode)) return;
78
78
  try {
79
79
  await analyzeFieldUsageInFile(sourceFilePath, sourceCode, pruneContext);
80
80
  } catch (parseError) {
@@ -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 INTLAYER_USAGE_REGEX,\n optimizeSourceFile,\n type PruneContext,\n renameFieldsInSourceFile,\n SOURCE_FILE_REGEX,\n} from '@intlayer/babel';\nimport {\n buildComponentFilesList,\n formatPath,\n runOnce,\n} from '@intlayer/chokidar/utils';\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 { getDictionaries } from '@intlayer/dictionaries-entry';\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): 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 = join(mainDir, 'dictionaries.mjs');\n const unmergedDictionariesEntryPath = join(\n mainDir,\n 'unmerged_dictionaries.mjs'\n );\n const dynamicDictionariesEntryPath = join(\n mainDir,\n 'dynamic_dictionaries.mjs'\n );\n\n const componentFilesList = buildComponentFilesList(intlayerConfig);\n\n const transformableFilesList = [\n ...componentFilesList,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\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 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 const isAnalysisEnabled = (_config: unknown, env: { command: string }) =>\n !editorEnabled &&\n (!!purge || !!minify) &&\n isBuildOptimizeEnabled(_config, env);\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 // 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 (!INTLAYER_USAGE_REGEX.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 );\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 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 (minify) {\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 dictionaries whose opaque fields have nested user-defined\n // structure – renaming those sub-keys would silently break child\n // components that consume the field value as-is.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(\n dictionaryKey\n );\n\n if (opaqueFieldMap) {\n const dangerousEntries = [...opaqueFieldMap.entries()].filter(\n ([fieldName]) =>\n (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0\n );\n if (dangerousEntries.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousEntries.flatMap(([fieldName, locations]) => [\n `\\n Opaque field:`,\n colorize(`'${fieldName}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...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 fields to prevent\n // breaking components that receive the field as a prop.\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\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 { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n },\n\n transform: async (sourceCode, moduleId) => {\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 = moduleId.split('?', 1)[0];\n\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;\n if (!transformableFilesList.includes(sourceFilePath)) return null;\n\n const isDictionaryEntryFile = [\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ].includes(sourceFilePath);\n\n const isUsingIntlayer = INTLAYER_USAGE_REGEX.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 (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: true,\n dictionaryModeMap: dictionaryKeyToImportModeMap,\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 INTLAYER_OR_COMPAT_USAGE_REGEX,\n INTLAYER_USAGE_REGEX,\n optimizeSourceFile,\n type PruneContext,\n renameFieldsInSourceFile,\n SOURCE_FILE_REGEX,\n} from '@intlayer/babel';\nimport {\n buildComponentFilesList,\n formatPath,\n runOnce,\n} from '@intlayer/chokidar/utils';\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 { getDictionaries } from '@intlayer/dictionaries-entry';\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): 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 = join(mainDir, 'dictionaries.mjs');\n const unmergedDictionariesEntryPath = join(\n mainDir,\n 'unmerged_dictionaries.mjs'\n );\n const dynamicDictionariesEntryPath = join(\n mainDir,\n 'dynamic_dictionaries.mjs'\n );\n\n const componentFilesList = buildComponentFilesList(intlayerConfig);\n\n const transformableFilesList = [\n ...componentFilesList,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\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 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 const isAnalysisEnabled = (_config: unknown, env: { command: string }) =>\n !editorEnabled &&\n (!!purge || !!minify) &&\n isBuildOptimizeEnabled(_config, env);\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 // 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 (!INTLAYER_OR_COMPAT_USAGE_REGEX.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 );\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 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 (minify) {\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 dictionaries whose opaque fields have nested user-defined\n // structure – renaming those sub-keys would silently break child\n // components that consume the field value as-is.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(\n dictionaryKey\n );\n\n if (opaqueFieldMap) {\n const dangerousEntries = [...opaqueFieldMap.entries()].filter(\n ([fieldName]) =>\n (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0\n );\n if (dangerousEntries.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousEntries.flatMap(([fieldName, locations]) => [\n `\\n Opaque field:`,\n colorize(`'${fieldName}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...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 fields to prevent\n // breaking components that receive the field as a prop.\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\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 { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n },\n\n transform: async (sourceCode, moduleId) => {\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 = moduleId.split('?', 1)[0];\n\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;\n if (!transformableFilesList.includes(sourceFilePath)) return null;\n\n const isDictionaryEntryFile = [\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ].includes(sourceFilePath);\n\n const isUsingIntlayer = INTLAYER_USAGE_REGEX.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 (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: true,\n dictionaryModeMap: dictionaryKeyToImportModeMap,\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 +1 @@
1
- {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { createPruneContext } from '@intlayer/babel';\nimport { prepareIntlayer } from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { watch } from '@intlayer/chokidar/watcher';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatNodeTypeToEnvVar,\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 { getAlias, getUnusedNodeTypesAsync } from '@intlayer/config/utils';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { PluginOption } from 'vite';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\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?: GetConfigurationOptions\n): PluginOption => {\n const intlayerConfig = getConfiguration(configOptions);\n logConfigDetails(configOptions);\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 // 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 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 // 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\\/|intlayer$)/],\n },\n };\n },\n\n configureServer: async (server) => {\n if (server.config.mode === 'development') {\n // Start watching (assuming watch is also async)\n await watch({ configuration: 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));\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 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,kBACX,kBACiB;CACjB,MAAM,iBAAiB,iBAAiB,aAAa;CACrD,iBAAiB,aAAa;CAC9B,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,QAE7C,MAAM,gBAAgB,gBAAgB;IACpC,OAAO;IACP,gBAAgB,iBACZ,MAAO,KACP,MAAO,KAAK;IAChB,KAAK,iBAAiB,SAAS;GACjC,CAAC;GAGH,IAAI,SAAiC,EAGnC,wBAAwB,WAC1B;GAEA,IAAI,gBAAgB;IAClB,MAAM,eAAe,gBAAgB,cAAc;IAEnD,IAAI,OAAO,KAAK,YAAY,EAAE,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,EAAE,SAAS,GAAG,CAC1D,EACC,KAAK,QAAQ,SAAS,KAAK,IAAI,CAAC,EAChC,KAAK,IAAI,CACd,GACA,EACE,WAAW,KACb,CACF;IAGF,SAAS;KACP,GAAG;KAGH,GAAG,uBACD,kBACC,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,0BAA0B,EACzC;GACF;EACF;EAEA,iBAAiB,OAAO,WAAW;GACjC,IAAI,OAAO,OAAO,SAAS,eAEzB,MAAM,MAAM,EAAE,eAAe,eAAe,CAAC;EAEjD;CACF,CACF;CAIA,MAAM,eAAe,mBAAmB;CAIxC,QAAQ,KAAK,iBAAiB,gBAAgB,YAAY,CAAC;CAK3D,QAAQ,KAAK,cAAc,gBAAgB,YAAY,CAAC;CAIxD,QAAQ,KAAK,eAAe,gBAAgB,YAAY,CAAC;CAEzD,OAAO;AACT;;;;;;;;;;;AAYA,MAAa,WAAW;;;;;;;;;;;;;AAaxB,MAAa,iBAAiB"}
1
+ {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { createPruneContext } from '@intlayer/babel';\nimport { prepareIntlayer } from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { watch } from '@intlayer/chokidar/watcher';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatNodeTypeToEnvVar,\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 { getAlias, getUnusedNodeTypesAsync } from '@intlayer/config/utils';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { PluginOption } from 'vite';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\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?: GetConfigurationOptions\n): PluginOption => {\n const intlayerConfig = getConfiguration(configOptions);\n logConfigDetails(configOptions);\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 // 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 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 // 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\\/|intlayer$)/],\n },\n };\n },\n\n configureServer: async (server) => {\n if (server.config.mode === 'development') {\n // Start watching (assuming watch is also async)\n await watch({ configuration: 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));\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 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,kBACX,kBACiB;CACjB,MAAM,iBAAiB,iBAAiB,cAAc;AACtD,kBAAiB,cAAc;CAC/B,MAAM,YAAY,aAAa,eAAe;CAE9C,MAAM,QAAQ,SAAS;EACrB,eAAe;EACf,YAAY,UAAkB,QAAQ,MAAM;EAC7C,CAAC;CAEF,MAAM,gBAAgB,OAAO,KAAK,MAAM;CAExC,MAAM,UAA0B,CAC9B;EACE,MAAM;EAEN,QAAQ,SAAS,QAAQ;GAEvB,MAAM,mBACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAG1C,MAAM,oBAAoB,eAAe,OAAO;AAEhD,UAAO,CAAC,oBAAoB;;EAG9B,QAAQ,OAAO,SAAS,QAAQ;GAC9B,MAAM,EAAE,SAAS,eAAe;GAEhC,MAAM,eACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAC1C,MAAM,iBAAiB,IAAI,YAAY;AAKvC,OAAI,gBAAgB,kBAAkB,SAAS,OAE7C,OAAM,gBAAgB,gBAAgB;IACpC,OAAO;IACP,gBAAgB,iBACZ,MAAO,KACP,MAAO,KAAK;IAChB,KAAK,iBAAiB,SAAS;IAChC,CAAC;GAGJ,IAAI,SAAiC,EAGnC,wBAAwB,YACzB;AAED,OAAI,gBAAgB;IAClB,MAAM,eAAe,gBAAgB,eAAe;AAEpD,QAAI,OAAO,KAAK,aAAa,CAAC,WAAW,EACvC,WACE,2DACA,EACE,WAAW,MACZ,CACF;IAGH,MAAM,kBAAkB,MAAM,wBAAwB,aAAa;AAEnE,QAAI,gBAAgB,SAAS,EAC3B,WACE,CACE,+BACA,gBACG,QACE,QACC,CAAC;KAAC;KAAa;KAAa;KAAa,CAAC,SAAS,IAAI,CAC1D,CACA,KAAK,QAAQ,SAAS,KAAK,KAAK,CAAC,CACjC,KAAK,KAAK,CACd,EACD,EACE,WAAW,MACZ,CACF;AAGH,aAAS;KACP,GAAG;KAGH,GAAG,uBACD,kBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,GACtB;KAGD,GAAG,iBACD,iBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,GACtB;KACF;;AAKH,UAAO;IACL;IACA,SAAS,EACP,OACD;IACD,cAAc,EAEZ,SAAS,eACV;IACD,KAAK,EAEH,YAAY,CAAC,2BAA2B,EACzC;IACF;;EAGH,iBAAiB,OAAO,WAAW;AACjC,OAAI,OAAO,OAAO,SAAS,cAEzB,OAAM,MAAM,EAAE,eAAe,gBAAgB,CAAC;;EAGnD,CACF;CAID,MAAM,eAAe,oBAAoB;AAIzC,SAAQ,KAAK,iBAAiB,gBAAgB,aAAa,CAAC;AAK5D,SAAQ,KAAK,cAAc,gBAAgB,aAAa,CAAC;AAIzD,SAAQ,KAAK,eAAe,gBAAgB,aAAa,CAAC;AAE1D,QAAO;;;;;;;;;;;;AAaT,MAAa,WAAW;;;;;;;;;;;;;AAaxB,MAAa,iBAAiB"}