vite-intlayer 8.4.4 → 8.4.5

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,2 +1,221 @@
1
- import{readFile as e}from"node:fs/promises";import{dirname as t,relative as n}from"node:path";import{detectPackageName as r,extractContent as i,getExtractPluginOptions as a,writeContentHelper as o}from"@intlayer/babel";import*as s from"@intlayer/config/colors";import{colorize as c,colorizeKey as l,colorizeNumber as u,colorizePath as d,getAppLogger as f,x as p}from"@intlayer/config/logger";import{getConfiguration as m}from"@intlayer/config/node";const h=h=>{let g,_,v,y=``,b=[],x=null,S=new Map,C=new Map,w=e=>{let t=S.get(e);return t?Date.now()-t<500:!1},T=e=>{S.set(e,Date.now());let t=Date.now();for(let[e,n]of S.entries())t-n>500*2&&S.delete(e)},E=e=>JSON.stringify(Object.keys(e).sort().map(t=>[t,e[t]])),D=(e,t)=>{let n=E(t);return C.get(e)===n?!1:(C.set(e,n),!0)},O=async()=>{b=_.filesList},k=async e=>{g=m(h?.configOptions),_=a(g,e),v=f(g),await O()},A=async e=>{let t=e.env?.DEV?`dev`:`build`;y=e.root,await k(t)},j=async()=>{try{v(`Intlayer compiler initialized`,{level:`info`})}catch(e){v(`${c(`Compiler:`,s.GREY_DARK)} Failed to prepare Intlayer: ${e}`,{level:`error`})}},M=async()=>{x&&await x},N=async({file:t,server:r,modules:i})=>{if(b.some(e=>e===t)){if(w(t)){v(`${c(`Compiler:`,s.GREY_DARK)} Skipping re-transform of ${d(n(y,t))} (recently processed)`,{level:`info`,isVerbose:!0});return}T(t);for(let e of i)r.moduleGraph.invalidateModule(e);try{await I(await e(t,`utf-8`),t)}catch(e){v(`${c(`Compiler:`,s.GREY_DARK)} Failed to re-transform ${t}: ${e}`,{level:`error`})}r.ws.send({type:`full-reload`})}},P=async e=>{let{dictionaryKey:t,content:n,filePath:r}=e;if(!D(t,n)){v(`${c(`Compiler:`,s.GREY_DARK)} Skipping dictionary ${l(t)} (content unchanged)`,{level:`info`,isVerbose:!0});return}try{await o(n,t,r,g)}catch(e){v(`${c(`Compiler:`,s.GREY_DARK)} Failed to write/build dictionary for ${l(t)}: ${e}`,{level:`error`})}},F=async e=>{let t=Object.keys(e.content);return v(`${c(`Compiler:`,s.GREY_DARK)} Extracted ${u(t.length)} content keys from ${d(n(y,e.filePath))}`,{level:`info`}),x=(x??Promise.resolve()).then(()=>P(e)).catch(e=>{v(`${c(`Compiler:`,s.GREY_DARK)} Error in dictionary write chain: ${e}`,{level:`error`})}),x},I=async(e,a)=>{if(!_.enabled||a.includes(`?`))return;let o=a;if(b.includes(o)){v(`${c(`Compiler:`,s.GREY_DARK)} Transforming ${d(n(y,o))}`,{level:`info`,isVerbose:!0});try{let n=await i(o,r(t(o)),{configuration:g,code:e,onExtract:async({key:e,content:t})=>{await F({dictionaryKey:e,content:t,filePath:o,locale:g.internationalization.defaultLocale})}});if(x&&await x,n?.transformedCode)return{code:n.transformedCode}}catch(e){v([`Failed to transform ${d(n(y,o))}:`,e],{level:`error`})}}};return{name:`vite-intlayer-compiler`,enforce:`pre`,configResolved:A,buildStart:j,buildEnd:M,handleHotUpdate:N,transform:I,apply:(e,t)=>(g||=m(h?.configOptions),v||=f(g),g.compiler.output?(_||=a(g,t.command),_.enabled):(v(`${p} No output configuration found. Add a ${c(`compiler.output`,s.BLUE)} in your configuration.`,{level:`error`}),!1))}};export{h as intlayerCompiler};
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, relative } from "node:path";
3
+ import { detectPackageName, extractContent, getExtractPluginOptions, writeContentHelper } from "@intlayer/babel";
4
+ import * as ANSIColors from "@intlayer/config/colors";
5
+ import { colorize, colorizeKey, colorizeNumber, colorizePath, getAppLogger, x } from "@intlayer/config/logger";
6
+ import { getConfiguration } from "@intlayer/config/node";
7
+
8
+ //#region src/IntlayerCompilerPlugin.ts
9
+ /**
10
+ * Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer
11
+ *
12
+ * This autonomous compiler handles:
13
+ * - Configuration loading and management
14
+ * - Hot Module Replacement (HMR) for content changes
15
+ * - File transformation with content extraction
16
+ * - Dictionary persistence and building
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // vite.config.ts
21
+ * import { defineConfig } from 'vite';
22
+ * import { intlayerCompiler } from 'vite-intlayer';
23
+ *
24
+ * export default defineConfig({
25
+ * plugins: [intlayerCompiler()],
26
+ * });
27
+ * ```
28
+ */
29
+ const intlayerCompiler = (options) => {
30
+ let config;
31
+ let compilerConfig;
32
+ let logger;
33
+ let projectRoot = "";
34
+ let filesList = [];
35
+ let pendingDictionaryWrite = null;
36
+ const recentlyProcessedFiles = /* @__PURE__ */ new Map();
37
+ const recentDictionaryContent = /* @__PURE__ */ new Map();
38
+ const DEBOUNCE_MS = 500;
39
+ /**
40
+ * Check if a file was recently processed (within debounce window)
41
+ * and should be skipped to prevent infinite loops
42
+ */
43
+ const wasRecentlyProcessed = (filePath) => {
44
+ const lastProcessed = recentlyProcessedFiles.get(filePath);
45
+ if (!lastProcessed) return false;
46
+ return Date.now() - lastProcessed < DEBOUNCE_MS;
47
+ };
48
+ /**
49
+ * Mark a file as recently processed
50
+ */
51
+ const markAsProcessed = (filePath) => {
52
+ recentlyProcessedFiles.set(filePath, Date.now());
53
+ const now = Date.now();
54
+ for (const [path, timestamp] of recentlyProcessedFiles.entries()) if (now - timestamp > DEBOUNCE_MS * 2) recentlyProcessedFiles.delete(path);
55
+ };
56
+ /**
57
+ * Create a simple hash of content for comparison
58
+ * Used to detect if dictionary content has actually changed
59
+ */
60
+ const hashContent = (content) => JSON.stringify(Object.keys(content).sort().map((key) => [key, content[key]]));
61
+ /**
62
+ * Check if dictionary content has changed since last write
63
+ */
64
+ const hasDictionaryContentChanged = (dictionaryKey, content) => {
65
+ const newHash = hashContent(content);
66
+ if (recentDictionaryContent.get(dictionaryKey) === newHash) return false;
67
+ recentDictionaryContent.set(dictionaryKey, newHash);
68
+ return true;
69
+ };
70
+ /**
71
+ * Build the list of files to transform based on configuration patterns
72
+ */
73
+ const buildFilesListFn = async () => {
74
+ filesList = compilerConfig.filesList;
75
+ };
76
+ /**
77
+ * Initialize the compiler with the given mode
78
+ */
79
+ const init = async (compilerMode) => {
80
+ config = getConfiguration(options?.configOptions);
81
+ compilerConfig = getExtractPluginOptions(config, compilerMode);
82
+ logger = getAppLogger(config);
83
+ await buildFilesListFn();
84
+ };
85
+ /**
86
+ * Vite hook: configResolved
87
+ * Called when Vite config is resolved
88
+ */
89
+ const configResolved = async (viteConfig) => {
90
+ const compilerMode = viteConfig.env?.DEV ? "dev" : "build";
91
+ projectRoot = viteConfig.root;
92
+ await init(compilerMode);
93
+ };
94
+ /**
95
+ * Build start hook - no longer needs to prepare dictionaries
96
+ * The compiler is now autonomous and extracts content inline
97
+ */
98
+ const buildStart = async () => {
99
+ try {
100
+ logger("Intlayer compiler initialized", { level: "info" });
101
+ } catch (error) {
102
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Failed to prepare Intlayer: ${error}`, { level: "error" });
103
+ }
104
+ };
105
+ /**
106
+ * Build end hook - wait for any pending dictionary writes
107
+ */
108
+ const buildEnd = async () => {
109
+ if (pendingDictionaryWrite) await pendingDictionaryWrite;
110
+ };
111
+ /**
112
+ * Vite hook: handleHotUpdate
113
+ * Handles HMR for content files - invalidates cache and triggers re-transform
114
+ */
115
+ const handleHotUpdate = async ({ file, server, modules }) => {
116
+ if (filesList.some((fileEl) => fileEl === file)) {
117
+ if (wasRecentlyProcessed(file)) {
118
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`, {
119
+ level: "info",
120
+ isVerbose: true
121
+ });
122
+ return;
123
+ }
124
+ markAsProcessed(file);
125
+ for (const mod of modules) server.moduleGraph.invalidateModule(mod);
126
+ try {
127
+ await transformHandler(await readFile(file, "utf-8"), file);
128
+ } catch (error) {
129
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`, { level: "error" });
130
+ }
131
+ server.ws.send({ type: "full-reload" });
132
+ }
133
+ };
134
+ /**
135
+ * Write and build one or more dictionaries based on extracted content.
136
+ * Leverages shared logic from @intlayer/babel.
137
+ */
138
+ const writeAndBuildDictionary = async (result) => {
139
+ const { dictionaryKey, content, filePath: sourceFilePath } = result;
140
+ if (!hasDictionaryContentChanged(dictionaryKey, content)) {
141
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`, {
142
+ level: "info",
143
+ isVerbose: true
144
+ });
145
+ return;
146
+ }
147
+ try {
148
+ await writeContentHelper(content, dictionaryKey, sourceFilePath, config);
149
+ } catch (error) {
150
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`, { level: "error" });
151
+ }
152
+ };
153
+ /**
154
+ * Callback for when content is extracted from a file
155
+ * Immediately writes and builds the dictionary
156
+ */
157
+ const handleExtractedContent = async (result) => {
158
+ const contentKeys = Object.keys(result.content);
159
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Extracted ${colorizeNumber(contentKeys.length)} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`, { level: "info" });
160
+ pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve()).then(() => writeAndBuildDictionary(result)).catch((error) => {
161
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`, { level: "error" });
162
+ });
163
+ return pendingDictionaryWrite;
164
+ };
165
+ /**
166
+ * Transform a file using the appropriate extraction plugin based on file type.
167
+ * Delegates to `extractContent` from `@intlayer/babel` which handles
168
+ * JS/TS/JSX/TSX/Vue/Svelte extraction and transformation.
169
+ */
170
+ const transformHandler = async (code, id) => {
171
+ if (!compilerConfig.enabled) return;
172
+ if (id.includes("?")) return;
173
+ const filename = id;
174
+ if (!filesList.includes(filename)) return;
175
+ logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`, {
176
+ level: "info",
177
+ isVerbose: true
178
+ });
179
+ try {
180
+ const result = await extractContent(filename, detectPackageName(dirname(filename)), {
181
+ configuration: config,
182
+ code,
183
+ onExtract: async ({ key, content }) => {
184
+ await handleExtractedContent({
185
+ dictionaryKey: key,
186
+ content,
187
+ filePath: filename,
188
+ locale: config.internationalization.defaultLocale
189
+ });
190
+ }
191
+ });
192
+ if (pendingDictionaryWrite) await pendingDictionaryWrite;
193
+ if (result?.transformedCode) return { code: result.transformedCode };
194
+ } catch (error) {
195
+ logger([`Failed to transform ${colorizePath(relative(projectRoot, filename))}:`, error], { level: "error" });
196
+ }
197
+ };
198
+ return {
199
+ name: "vite-intlayer-compiler",
200
+ enforce: "pre",
201
+ configResolved,
202
+ buildStart,
203
+ buildEnd,
204
+ handleHotUpdate,
205
+ transform: transformHandler,
206
+ apply: (_viteConfig, env) => {
207
+ if (!config) config = getConfiguration(options?.configOptions);
208
+ if (!logger) logger = getAppLogger(config);
209
+ if (!config.compiler.output) {
210
+ logger(`${x} No output configuration found. Add a ${colorize("compiler.output", ANSIColors.BLUE)} in your configuration.`, { level: "error" });
211
+ return false;
212
+ }
213
+ if (!compilerConfig) compilerConfig = getExtractPluginOptions(config, env.command);
214
+ return compilerConfig.enabled;
215
+ }
216
+ };
217
+ };
218
+
219
+ //#endregion
220
+ export { intlayerCompiler };
2
221
  //# sourceMappingURL=IntlayerCompilerPlugin.mjs.map
@@ -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 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 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 +1,7 @@
1
- import{intlayerCompiler as e}from"./IntlayerCompilerPlugin.mjs";import{intlayerEditorPlugin as t}from"./intlayerEditorPlugin.mjs";import{intlayerOptimize as n}from"./intlayerOptimizePlugin.mjs";import{intLayerPlugin as r,intlayer as i,intlayerPlugin as a}from"./intlayerPlugin.mjs";import{intLayerMiddlewarePlugin as o,intlayerMiddleware as s,intlayerProxy as c}from"./intlayerProxyPlugin.mjs";export{o as intLayerMiddlewarePlugin,r as intLayerPlugin,i as intlayer,e as intlayerCompiler,t as intlayerEditorPlugin,s as intlayerMiddleware,n as intlayerOptimize,a as intlayerPlugin,c as intlayerProxy};
1
+ import { intlayerCompiler } from "./IntlayerCompilerPlugin.mjs";
2
+ import { intlayerEditorPlugin } from "./intlayerEditorPlugin.mjs";
3
+ import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
4
+ import { intLayerPlugin, intlayer, intlayerPlugin } from "./intlayerPlugin.mjs";
5
+ import { intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.mjs";
6
+
7
+ export { intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerEditorPlugin, intlayerMiddleware, intlayerOptimize, intlayerPlugin, intlayerProxy };
@@ -1,2 +1,17 @@
1
- const e=e=>({name:`vite-intlayer-editor-plugin`,config:()=>({define:{"process.env.INTLAYER_EDITOR_ENABLED":JSON.stringify(e.editor?.enabled===!1?`false`:`true`)}})});export{e as intlayerEditorPlugin};
1
+ //#region src/intlayerEditorPlugin.ts
2
+ /**
3
+ * Vite plugin that injects `process.env.INTLAYER_EDITOR_ENABLED` as a
4
+ * compile-time constant so bundlers can dead-code-eliminate all
5
+ * `import('@intlayer/editor')` calls when the editor is disabled.
6
+ *
7
+ * Returning a partial config object from the `config` hook is the idiomatic
8
+ * Vite approach — Vite deep-merges it automatically via `mergeConfig`.
9
+ */
10
+ const intlayerEditorPlugin = (intlayerConfig) => ({
11
+ name: "vite-intlayer-editor-plugin",
12
+ config: () => ({ define: { "process.env.INTLAYER_EDITOR_ENABLED": JSON.stringify(intlayerConfig.editor?.enabled === false ? "false" : "true") } })
13
+ });
14
+
15
+ //#endregion
16
+ export { intlayerEditorPlugin };
2
17
  //# sourceMappingURL=intlayerEditorPlugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerEditorPlugin.mjs","names":[],"sources":["../../src/intlayerEditorPlugin.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\n// @ts-ignore - Fix error Module '\"vite\"' has no exported member\nimport type { Plugin } from 'vite';\n\n/**\n * Vite plugin that injects `process.env.INTLAYER_EDITOR_ENABLED` as a\n * compile-time constant so bundlers can dead-code-eliminate all\n * `import('@intlayer/editor')` calls when the editor is disabled.\n *\n * Returning a partial config object from the `config` hook is the idiomatic\n * Vite approach — Vite deep-merges it automatically via `mergeConfig`.\n */\nexport const intlayerEditorPlugin = (\n intlayerConfig: IntlayerConfig\n): Plugin => ({\n name: 'vite-intlayer-editor-plugin',\n config: () => ({\n define: {\n 'process.env.INTLAYER_EDITOR_ENABLED': JSON.stringify(\n intlayerConfig.editor?.enabled === false ? 'false' : 'true'\n ),\n },\n }),\n});\n"],"mappings":"AAYA,MAAa,EACX,IACY,CACZ,KAAM,8BACN,YAAe,CACb,OAAQ,CACN,sCAAuC,KAAK,UAC1C,EAAe,QAAQ,UAAY,GAAQ,QAAU,OACtD,CACF,CACF,EACF"}
1
+ {"version":3,"file":"intlayerEditorPlugin.mjs","names":[],"sources":["../../src/intlayerEditorPlugin.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\n// @ts-ignore - Fix error Module '\"vite\"' has no exported member\nimport type { Plugin } from 'vite';\n\n/**\n * Vite plugin that injects `process.env.INTLAYER_EDITOR_ENABLED` as a\n * compile-time constant so bundlers can dead-code-eliminate all\n * `import('@intlayer/editor')` calls when the editor is disabled.\n *\n * Returning a partial config object from the `config` hook is the idiomatic\n * Vite approach — Vite deep-merges it automatically via `mergeConfig`.\n */\nexport const intlayerEditorPlugin = (\n intlayerConfig: IntlayerConfig\n): Plugin => ({\n name: 'vite-intlayer-editor-plugin',\n config: () => ({\n define: {\n 'process.env.INTLAYER_EDITOR_ENABLED': JSON.stringify(\n intlayerConfig.editor?.enabled === false ? 'false' : 'true'\n ),\n },\n }),\n});\n"],"mappings":";;;;;;;;;AAYA,MAAa,wBACX,oBACY;CACZ,MAAM;CACN,eAAe,EACb,QAAQ,EACN,uCAAuC,KAAK,UAC1C,eAAe,QAAQ,YAAY,QAAQ,UAAU,OACtD,EACF,EACF;CACF"}
@@ -1,2 +1,103 @@
1
- import{intlayerVueAsyncPlugin as e}from"./intlayerVueAsyncPlugin.mjs";import{join as t}from"node:path";import{intlayerOptimizeBabelPlugin as n}from"@intlayer/babel";import{getAppLogger as r}from"@intlayer/config/logger";import{transformSync as i}from"@babel/core";import{buildComponentFilesList as a,runOnce as o}from"@intlayer/chokidar/utils";import{IMPORT_MODE as s}from"@intlayer/config/defaultValues";import{getDictionaries as c}from"@intlayer/dictionaries-entry";const l=/\b(use|get)Intlayer\b/,u=async u=>{try{let d=r(u),{optimize:f}=u.build,p=u.build.importMode??u.dictionary?.importMode,{dictionariesDir:m,dynamicDictionariesDir:h,unmergedDictionariesDir:g,fetchDictionariesDir:_,mainDir:v}=u.system,{baseDir:y}=u.system,b=t(v,`dictionaries.mjs`),x=t(v,`unmerged_dictionaries.mjs`),S=t(v,`dynamic_dictionaries.mjs`),C=[...a(u),b,x],w=c(u),T={};return Object.values(w).forEach(e=>{T[e.key]=e.importMode??p??s}),[e(u,C),{name:`vite-intlayer-babel-transform`,enforce:`post`,apply:(e,n)=>{let r=n.command===`build`,i=f===void 0&&r||f===!0;return i&&o(t(y,`.intlayer`,`cache`,`intlayer-prune-plugin-enabled.lock`),()=>d(`Build optimization enabled`),{cacheTimeoutMs:1e3*10}),i},transform(e,t){let r=t.split(`?`,1)[0];if(!C.includes(r))return null;let a=[b,x].includes(r);if(!(l.test(e)||a))return null;let o=i(e,{filename:r,plugins:[[n,{optimize:f,dictionariesDir:m,dictionariesEntryPath:b,unmergedDictionariesEntryPath:x,unmergedDictionariesDir:g,dynamicDictionariesDir:h,dynamicDictionariesEntryPath:S,fetchDictionariesDir:_,importMode:p,filesList:C,replaceDictionaryEntry:!0,dictionaryModeMap:T}]],parserOpts:{sourceType:`module`,allowImportExportEverywhere:!0,plugins:[`typescript`,`jsx`,`decorators-legacy`,`classProperties`,`objectRestSpread`,`asyncGenerators`,`functionBind`,`exportDefaultFrom`,`exportNamespaceFrom`,`dynamicImport`,`nullishCoalescingOperator`,`optionalChaining`]}});if(o?.code)return{code:o.code,map:o.map}}}]}catch(e){return console.warn(`Failed to transform with Babel plugin:`,e),[]}};export{u as intlayerOptimize};
1
+ import { intlayerVueAsyncPlugin } from "./intlayerVueAsyncPlugin.mjs";
2
+ import { join } from "node:path";
3
+ import { intlayerOptimizeBabelPlugin } from "@intlayer/babel";
4
+ import { getAppLogger } from "@intlayer/config/logger";
5
+ import { transformSync } from "@babel/core";
6
+ import { buildComponentFilesList, runOnce } from "@intlayer/chokidar/utils";
7
+ import { IMPORT_MODE } from "@intlayer/config/defaultValues";
8
+ import { getDictionaries } from "@intlayer/dictionaries-entry";
9
+
10
+ //#region src/intlayerOptimizePlugin.ts
11
+ const INTLAYER_USAGE_REGEX = /\b(use|get)Intlayer\b/;
12
+ const intlayerOptimize = async (intlayerConfig) => {
13
+ try {
14
+ const logger = getAppLogger(intlayerConfig);
15
+ const { optimize } = intlayerConfig.build;
16
+ const importMode = intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;
17
+ const { dictionariesDir, dynamicDictionariesDir, unmergedDictionariesDir, fetchDictionariesDir, mainDir } = intlayerConfig.system;
18
+ const { baseDir } = intlayerConfig.system;
19
+ const dictionariesEntryPath = join(mainDir, "dictionaries.mjs");
20
+ const unmergedDictionariesEntryPath = join(mainDir, "unmerged_dictionaries.mjs");
21
+ const dynamicDictionariesEntryPath = join(mainDir, "dynamic_dictionaries.mjs");
22
+ const filesList = [
23
+ ...buildComponentFilesList(intlayerConfig),
24
+ dictionariesEntryPath,
25
+ unmergedDictionariesEntryPath
26
+ ];
27
+ const dictionaries = getDictionaries(intlayerConfig);
28
+ const dictionaryModeMap = {};
29
+ Object.values(dictionaries).forEach((dictionary) => {
30
+ dictionaryModeMap[dictionary.key] = dictionary.importMode ?? importMode ?? IMPORT_MODE;
31
+ });
32
+ return [intlayerVueAsyncPlugin(intlayerConfig, filesList), {
33
+ name: "vite-intlayer-babel-transform",
34
+ enforce: "post",
35
+ apply: (_config, env) => {
36
+ const isBuild = env.command === "build";
37
+ const isEnabled = optimize === void 0 && isBuild || optimize === true;
38
+ if (isEnabled) runOnce(join(baseDir, ".intlayer", "cache", "intlayer-prune-plugin-enabled.lock"), () => logger("Build optimization enabled"), { cacheTimeoutMs: 1e3 * 10 });
39
+ return isEnabled;
40
+ },
41
+ transform(code, id) {
42
+ /**
43
+ * Transform file as
44
+ * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts
45
+ * Into
46
+ * .../HelloWorld.vue
47
+ *
48
+ * Prevention for virtual file
49
+ */
50
+ const filename = id.split("?", 1)[0];
51
+ if (!filesList.includes(filename)) return null;
52
+ const isDictionaryEntry = [dictionariesEntryPath, unmergedDictionariesEntryPath].includes(filename);
53
+ if (!(INTLAYER_USAGE_REGEX.test(code) || isDictionaryEntry)) return null;
54
+ const result = transformSync(code, {
55
+ filename,
56
+ plugins: [[intlayerOptimizeBabelPlugin, {
57
+ optimize,
58
+ dictionariesDir,
59
+ dictionariesEntryPath,
60
+ unmergedDictionariesEntryPath,
61
+ unmergedDictionariesDir,
62
+ dynamicDictionariesDir,
63
+ dynamicDictionariesEntryPath,
64
+ fetchDictionariesDir,
65
+ importMode,
66
+ filesList,
67
+ replaceDictionaryEntry: true,
68
+ dictionaryModeMap
69
+ }]],
70
+ parserOpts: {
71
+ sourceType: "module",
72
+ allowImportExportEverywhere: true,
73
+ plugins: [
74
+ "typescript",
75
+ "jsx",
76
+ "decorators-legacy",
77
+ "classProperties",
78
+ "objectRestSpread",
79
+ "asyncGenerators",
80
+ "functionBind",
81
+ "exportDefaultFrom",
82
+ "exportNamespaceFrom",
83
+ "dynamicImport",
84
+ "nullishCoalescingOperator",
85
+ "optionalChaining"
86
+ ]
87
+ }
88
+ });
89
+ if (result?.code) return {
90
+ code: result.code,
91
+ map: result.map
92
+ };
93
+ }
94
+ }];
95
+ } catch (error) {
96
+ console.warn("Failed to transform with Babel plugin:", error);
97
+ return [];
98
+ }
99
+ };
100
+
101
+ //#endregion
102
+ export { intlayerOptimize };
2
103
  //# sourceMappingURL=intlayerOptimizePlugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { transformSync } from '@babel/core';\nimport { intlayerOptimizeBabelPlugin } from '@intlayer/babel';\nimport { buildComponentFilesList, runOnce } from '@intlayer/chokidar/utils';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport { getAppLogger } 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\nconst INTLAYER_USAGE_REGEX = /\\b(use|get)Intlayer\\b/;\n\nexport const intlayerOptimize = async (\n intlayerConfig: IntlayerConfig\n): Promise<PluginOption[]> => {\n try {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize } = intlayerConfig.build;\n const importMode =\n intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n unmergedDictionariesDir,\n fetchDictionariesDir,\n mainDir,\n } = intlayerConfig.system;\n const { baseDir } = 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 baseFilesList = buildComponentFilesList(intlayerConfig);\n\n const filesList = [\n ...baseFilesList,\n dictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic\n unmergedDictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic\n ];\n\n const dictionaries = getDictionaries(intlayerConfig);\n\n const dictionaryModeMap: Record<string, 'static' | 'dynamic' | 'fetch'> =\n {};\n\n (Object.values(dictionaries) as Dictionary[]).forEach((dictionary) => {\n dictionaryModeMap[dictionary.key] =\n dictionary.importMode ?? importMode ?? IMPORT_MODE;\n });\n\n return [\n intlayerVueAsyncPlugin(intlayerConfig, filesList),\n {\n name: 'vite-intlayer-babel-transform',\n enforce: 'post', // Run after other transformations as vue\n apply: (_config, env) => {\n // Only apply babel plugin if optimize is enabled\n\n const isBuild = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuild) || optimize === true;\n\n if (isEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-prune-plugin-enabled.lock'\n ),\n () => logger('Build optimization enabled'),\n {\n cacheTimeoutMs: 1000 * 10, // 10 seconds\n }\n );\n }\n\n return isEnabled;\n },\n transform(code, id) {\n /**\n * Transform file as\n * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts\n * Into\n * .../HelloWorld.vue\n *\n * Prevention for virtual file\n */\n const filename = id.split('?', 1)[0];\n\n if (!filesList.includes(filename)) return null;\n\n const isDictionaryEntry = [\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ].includes(filename);\n\n const isUsingIntlayer = INTLAYER_USAGE_REGEX.test(code);\n\n const shouldTransform = isUsingIntlayer || isDictionaryEntry;\n\n if (!shouldTransform) return null;\n\n const result = transformSync(code, {\n filename,\n plugins: [\n [\n intlayerOptimizeBabelPlugin,\n {\n optimize,\n dictionariesDir,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n unmergedDictionariesDir,\n dynamicDictionariesDir,\n dynamicDictionariesEntryPath,\n fetchDictionariesDir,\n importMode,\n filesList,\n replaceDictionaryEntry: true,\n dictionaryModeMap,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n },\n },\n ];\n } catch (error) {\n console.warn('Failed to transform with Babel plugin:', error);\n\n return [];\n }\n};\n"],"mappings":"odAYA,MAAM,EAAuB,wBAEhB,EAAmB,KAC9B,IAC4B,CAC5B,GAAI,CACF,IAAM,EAAS,EAAa,EAAe,CAErC,CAAE,YAAa,EAAe,MAC9B,EACJ,EAAe,MAAM,YAAc,EAAe,YAAY,WAE1D,CACJ,kBACA,yBACA,0BACA,uBACA,WACE,EAAe,OACb,CAAE,WAAY,EAAe,OAE7B,EAAwB,EAAK,EAAS,mBAAmB,CACzD,EAAgC,EACpC,EACA,4BACD,CACK,EAA+B,EACnC,EACA,2BACD,CAIK,EAAY,CAChB,GAHoB,EAAwB,EAAe,CAI3D,EACA,EACD,CAEK,EAAe,EAAgB,EAAe,CAE9C,EACJ,EAAE,CAOJ,OALC,OAAO,OAAO,EAAa,CAAkB,QAAS,GAAe,CACpE,EAAkB,EAAW,KAC3B,EAAW,YAAc,GAAc,GACzC,CAEK,CACL,EAAuB,EAAgB,EAAU,CACjD,CACE,KAAM,gCACN,QAAS,OACT,OAAQ,EAAS,IAAQ,CAGvB,IAAM,EAAU,EAAI,UAAY,QAC1B,EACH,IAAa,IAAA,IAAa,GAAY,IAAa,GAiBtD,OAfI,GACF,EACE,EACE,EACA,YACA,QACA,qCACD,KACK,EAAO,6BAA6B,CAC1C,CACE,eAAgB,IAAO,GACxB,CACF,CAGI,GAET,UAAU,EAAM,EAAI,CASlB,IAAM,EAAW,EAAG,MAAM,IAAK,EAAE,CAAC,GAElC,GAAI,CAAC,EAAU,SAAS,EAAS,CAAE,OAAO,KAE1C,IAAM,EAAoB,CACxB,EACA,EACD,CAAC,SAAS,EAAS,CAMpB,GAAI,EAJoB,EAAqB,KAAK,EAAK,EAEZ,GAErB,OAAO,KAE7B,IAAM,EAAS,EAAc,EAAM,CACjC,WACA,QAAS,CACP,CACE,EACA,CACE,WACA,kBACA,wBACA,gCACA,0BACA,yBACA,+BACA,uBACA,aACA,YACA,uBAAwB,GACxB,oBACD,CACF,CACF,CACD,WAAY,CACV,WAAY,SACZ,4BAA6B,GAC7B,QAAS,CACP,aACA,MACA,oBACA,kBACA,mBACA,kBACA,eACA,oBACA,sBACA,gBACA,4BACA,mBACD,CACF,CACF,CAAC,CAEF,GAAI,GAAQ,KACV,MAAO,CACL,KAAM,EAAO,KACb,IAAK,EAAO,IACb,EAGN,CACF,OACM,EAAO,CAGd,OAFA,QAAQ,KAAK,yCAA0C,EAAM,CAEtD,EAAE"}
1
+ {"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { transformSync } from '@babel/core';\nimport { intlayerOptimizeBabelPlugin } from '@intlayer/babel';\nimport { buildComponentFilesList, runOnce } from '@intlayer/chokidar/utils';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport { getAppLogger } 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\nconst INTLAYER_USAGE_REGEX = /\\b(use|get)Intlayer\\b/;\n\nexport const intlayerOptimize = async (\n intlayerConfig: IntlayerConfig\n): Promise<PluginOption[]> => {\n try {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize } = intlayerConfig.build;\n const importMode =\n intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n unmergedDictionariesDir,\n fetchDictionariesDir,\n mainDir,\n } = intlayerConfig.system;\n const { baseDir } = 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 baseFilesList = buildComponentFilesList(intlayerConfig);\n\n const filesList = [\n ...baseFilesList,\n dictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic\n unmergedDictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic\n ];\n\n const dictionaries = getDictionaries(intlayerConfig);\n\n const dictionaryModeMap: Record<string, 'static' | 'dynamic' | 'fetch'> =\n {};\n\n (Object.values(dictionaries) as Dictionary[]).forEach((dictionary) => {\n dictionaryModeMap[dictionary.key] =\n dictionary.importMode ?? importMode ?? IMPORT_MODE;\n });\n\n return [\n intlayerVueAsyncPlugin(intlayerConfig, filesList),\n {\n name: 'vite-intlayer-babel-transform',\n enforce: 'post', // Run after other transformations as vue\n apply: (_config, env) => {\n // Only apply babel plugin if optimize is enabled\n\n const isBuild = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuild) || optimize === true;\n\n if (isEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-prune-plugin-enabled.lock'\n ),\n () => logger('Build optimization enabled'),\n {\n cacheTimeoutMs: 1000 * 10, // 10 seconds\n }\n );\n }\n\n return isEnabled;\n },\n transform(code, id) {\n /**\n * Transform file as\n * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts\n * Into\n * .../HelloWorld.vue\n *\n * Prevention for virtual file\n */\n const filename = id.split('?', 1)[0];\n\n if (!filesList.includes(filename)) return null;\n\n const isDictionaryEntry = [\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ].includes(filename);\n\n const isUsingIntlayer = INTLAYER_USAGE_REGEX.test(code);\n\n const shouldTransform = isUsingIntlayer || isDictionaryEntry;\n\n if (!shouldTransform) return null;\n\n const result = transformSync(code, {\n filename,\n plugins: [\n [\n intlayerOptimizeBabelPlugin,\n {\n optimize,\n dictionariesDir,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n unmergedDictionariesDir,\n dynamicDictionariesDir,\n dynamicDictionariesEntryPath,\n fetchDictionariesDir,\n importMode,\n filesList,\n replaceDictionaryEntry: true,\n dictionaryModeMap,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n },\n },\n ];\n } catch (error) {\n console.warn('Failed to transform with Babel plugin:', error);\n\n return [];\n }\n};\n"],"mappings":";;;;;;;;;;AAYA,MAAM,uBAAuB;AAE7B,MAAa,mBAAmB,OAC9B,mBAC4B;AAC5B,KAAI;EACF,MAAM,SAAS,aAAa,eAAe;EAE3C,MAAM,EAAE,aAAa,eAAe;EACpC,MAAM,aACJ,eAAe,MAAM,cAAc,eAAe,YAAY;EAEhE,MAAM,EACJ,iBACA,wBACA,yBACA,sBACA,YACE,eAAe;EACnB,MAAM,EAAE,YAAY,eAAe;EAEnC,MAAM,wBAAwB,KAAK,SAAS,mBAAmB;EAC/D,MAAM,gCAAgC,KACpC,SACA,4BACD;EACD,MAAM,+BAA+B,KACnC,SACA,2BACD;EAID,MAAM,YAAY;GAChB,GAHoB,wBAAwB,eAAe;GAI3D;GACA;GACD;EAED,MAAM,eAAe,gBAAgB,eAAe;EAEpD,MAAM,oBACJ,EAAE;AAEJ,EAAC,OAAO,OAAO,aAAa,CAAkB,SAAS,eAAe;AACpE,qBAAkB,WAAW,OAC3B,WAAW,cAAc,cAAc;IACzC;AAEF,SAAO,CACL,uBAAuB,gBAAgB,UAAU,EACjD;GACE,MAAM;GACN,SAAS;GACT,QAAQ,SAAS,QAAQ;IAGvB,MAAM,UAAU,IAAI,YAAY;IAChC,MAAM,YACH,aAAa,UAAa,WAAY,aAAa;AAEtD,QAAI,UACF,SACE,KACE,SACA,aACA,SACA,qCACD,QACK,OAAO,6BAA6B,EAC1C,EACE,gBAAgB,MAAO,IACxB,CACF;AAGH,WAAO;;GAET,UAAU,MAAM,IAAI;;;;;;;;;IASlB,MAAM,WAAW,GAAG,MAAM,KAAK,EAAE,CAAC;AAElC,QAAI,CAAC,UAAU,SAAS,SAAS,CAAE,QAAO;IAE1C,MAAM,oBAAoB,CACxB,uBACA,8BACD,CAAC,SAAS,SAAS;AAMpB,QAAI,EAJoB,qBAAqB,KAAK,KAAK,IAEZ,mBAErB,QAAO;IAE7B,MAAM,SAAS,cAAc,MAAM;KACjC;KACA,SAAS,CACP,CACE,6BACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA,wBAAwB;MACxB;MACD,CACF,CACF;KACD,YAAY;MACV,YAAY;MACZ,6BAA6B;MAC7B,SAAS;OACP;OACA;OACA;OACA;OACA;OACA;OACA;OACA;OACA;OACA;OACA;OACA;OACD;MACF;KACF,CAAC;AAEF,QAAI,QAAQ,KACV,QAAO;KACL,MAAM,OAAO;KACb,KAAK,OAAO;KACb;;GAGN,CACF;UACM,OAAO;AACd,UAAQ,KAAK,0CAA0C,MAAM;AAE7D,SAAO,EAAE"}
@@ -1,2 +1,95 @@
1
- import{intlayerEditorPlugin as e}from"./intlayerEditorPlugin.mjs";import{intlayerOptimize as t}from"./intlayerOptimizePlugin.mjs";import{resolve as n}from"node:path";import{getConfiguration as r}from"@intlayer/config/node";import{prepareIntlayer as i}from"@intlayer/chokidar/build";import{logConfigDetails as a}from"@intlayer/chokidar/cli";import{watch as o}from"@intlayer/chokidar/watcher";import{getAlias as s}from"@intlayer/config/utils";import{mergeConfig as c}from"vite";const l=l=>{let u=r(l);a(l);let d=s({configuration:u,formatter:e=>n(e)}),f=Object.keys(d),p=[{name:`vite-intlayer-plugin`,config:async(e,t)=>{let{mode:n}=u.build,r=t.command===`serve`&&t.mode===`development`,a=t.command===`build`;return(r||a||n===`auto`)&&await i(u,{clean:a,cacheTimeoutMs:a?1e3*30:1e3*60*60,env:a?`prod`:`dev`}),c(e,{resolve:{alias:d},optimizeDeps:{exclude:f},ssr:{noExternal:[/(^@intlayer\/|intlayer$)/]}})},configureServer:async e=>{e.config.mode===`development`&&o({configuration:u})}}];return p.push(e(u)),p.push(t(u)),p},u=l,d=l;export{d as intLayerPlugin,u as intlayer,l as intlayerPlugin};
1
+ import { intlayerEditorPlugin } from "./intlayerEditorPlugin.mjs";
2
+ import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
3
+ import { resolve } from "node:path";
4
+ import { getConfiguration } from "@intlayer/config/node";
5
+ import { prepareIntlayer } from "@intlayer/chokidar/build";
6
+ import { logConfigDetails } from "@intlayer/chokidar/cli";
7
+ import { watch } from "@intlayer/chokidar/watcher";
8
+ import { getAlias } from "@intlayer/config/utils";
9
+ import { mergeConfig } from "vite";
10
+
11
+ //#region src/intlayerPlugin.ts
12
+ /**
13
+ * Vite plugin that integrates Intlayer into the Vite build process.
14
+ *
15
+ * It handles:
16
+ * 1. Preparing Intlayer resources (dictionaries) before build.
17
+ * 2. Configuring Vite aliases for dictionary access.
18
+ * 3. Setting up dev-server watchers for content changes.
19
+ * 4. Applying build optimizations (tree-shaking dictionaries).
20
+ *
21
+ * @param configOptions - Optional configuration to override default Intlayer settings.
22
+ * @returns A Vite plugin option.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { intlayer } from 'vite-intlayer';
27
+ *
28
+ * export default defineConfig({
29
+ * plugins: [intlayer()],
30
+ * });
31
+ *
32
+ * ```
33
+ * @deprecated Rename to intlayer instead
34
+ */
35
+ const intlayerPlugin = (configOptions) => {
36
+ const intlayerConfig = getConfiguration(configOptions);
37
+ logConfigDetails(configOptions);
38
+ const alias = getAlias({
39
+ configuration: intlayerConfig,
40
+ formatter: (value) => resolve(value)
41
+ });
42
+ const aliasPackages = Object.keys(alias);
43
+ const plugins = [{
44
+ name: "vite-intlayer-plugin",
45
+ config: async (config, env) => {
46
+ const { mode } = intlayerConfig.build;
47
+ const isDevCommand = env.command === "serve" && env.mode === "development";
48
+ const isBuildCommand = env.command === "build";
49
+ if (isDevCommand || isBuildCommand || mode === "auto") await prepareIntlayer(intlayerConfig, {
50
+ clean: isBuildCommand,
51
+ cacheTimeoutMs: isBuildCommand ? 1e3 * 30 : 1e3 * 60 * 60,
52
+ env: isBuildCommand ? "prod" : "dev"
53
+ });
54
+ return mergeConfig(config, {
55
+ resolve: { alias },
56
+ optimizeDeps: { exclude: aliasPackages },
57
+ ssr: { noExternal: [/(^@intlayer\/|intlayer$)/] }
58
+ });
59
+ },
60
+ configureServer: async (server) => {
61
+ if (server.config.mode === "development") watch({ configuration: intlayerConfig });
62
+ }
63
+ }];
64
+ plugins.push(intlayerEditorPlugin(intlayerConfig));
65
+ plugins.push(intlayerOptimize(intlayerConfig));
66
+ return plugins;
67
+ };
68
+ /**
69
+ * A Vite plugin that integrates Intlayer configuration into the build process
70
+ *
71
+ * ```ts
72
+ * // Example usage of the plugin in a Vite configuration
73
+ * export default defineConfig({
74
+ * plugins: [ intlayer() ],
75
+ * });
76
+ * ```
77
+ */
78
+ const intlayer = intlayerPlugin;
79
+ /**
80
+ * @deprecated Rename to intlayer instead
81
+ *
82
+ * A Vite plugin that integrates Intlayer configuration into the build process
83
+ *
84
+ * ```ts
85
+ * // Example usage of the plugin in a Vite configuration
86
+ * export default defineConfig({
87
+ * plugins: [ intlayer() ],
88
+ * });
89
+ * ```
90
+ */
91
+ const intLayerPlugin = intlayerPlugin;
92
+
93
+ //#endregion
94
+ export { intLayerPlugin, intlayer, intlayerPlugin };
2
95
  //# sourceMappingURL=intlayerPlugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { prepareIntlayer } from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { watch } from '@intlayer/chokidar/watcher';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getAlias } from '@intlayer/config/utils';\n// @ts-ignore - Fix error Module '\"vite\"' has no exported member\nimport { mergeConfig, type PluginOption } from 'vite';\nimport { intlayerEditorPlugin } from './intlayerEditorPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\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\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 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 `start`)\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 // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return mergeConfig(config, {\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 watch({ configuration: intlayerConfig });\n }\n },\n },\n ];\n\n plugins.push(intlayerEditorPlugin(intlayerConfig));\n\n // Add Babel transform plugin if enabled\n plugins.push(intlayerOptimize(intlayerConfig));\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":"4dAqCA,MAAa,EACX,GACiB,CACjB,IAAM,EAAiB,EAAiB,EAAc,CACtD,EAAiB,EAAc,CAE/B,IAAM,EAAQ,EAAS,CACrB,cAAe,EACf,UAAY,GAAkB,EAAQ,EAAM,CAC7C,CAAC,CAEI,EAAgB,OAAO,KAAK,EAAM,CAElC,EAA0B,CAC9B,CACE,KAAM,uBAEN,OAAQ,MAAO,EAAQ,IAAQ,CAC7B,GAAM,CAAE,QAAS,EAAe,MAE1B,EACJ,EAAI,UAAY,SAAW,EAAI,OAAS,cACpC,EAAiB,EAAI,UAAY,QAkBvC,OAbI,GAAgB,GAAkB,IAAS,SAE7C,MAAM,EAAgB,EAAgB,CACpC,MAAO,EACP,eAAgB,EACZ,IAAO,GACP,IAAO,GAAK,GAChB,IAAK,EAAiB,OAAS,MAChC,CAAC,CAKG,EAAY,EAAQ,CACzB,QAAS,CACP,QACD,CACD,aAAc,CAEZ,QAAS,EACV,CACD,IAAK,CAEH,WAAY,CAAC,2BAA2B,CACzC,CACF,CAAC,EAGJ,gBAAiB,KAAO,IAAW,CAC7B,EAAO,OAAO,OAAS,eAEzB,EAAM,CAAE,cAAe,EAAgB,CAAC,EAG7C,CACF,CAOD,OALA,EAAQ,KAAK,EAAqB,EAAe,CAAC,CAGlD,EAAQ,KAAK,EAAiB,EAAe,CAAC,CAEvC,GAaI,EAAW,EAaX,EAAiB"}
1
+ {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { prepareIntlayer } from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { watch } from '@intlayer/chokidar/watcher';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getAlias } from '@intlayer/config/utils';\n// @ts-ignore - Fix error Module '\"vite\"' has no exported member\nimport { mergeConfig, type PluginOption } from 'vite';\nimport { intlayerEditorPlugin } from './intlayerEditorPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\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\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 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 `start`)\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 // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return mergeConfig(config, {\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 watch({ configuration: intlayerConfig });\n }\n },\n },\n ];\n\n plugins.push(intlayerEditorPlugin(intlayerConfig));\n\n // Add Babel transform plugin if enabled\n plugins.push(intlayerOptimize(intlayerConfig));\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,kBACX,kBACiB;CACjB,MAAM,iBAAiB,iBAAiB,cAAc;AACtD,kBAAiB,cAAc;CAE/B,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,OAAO,QAAQ,QAAQ;GAC7B,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;AAKJ,UAAO,YAAY,QAAQ;IACzB,SAAS,EACP,OACD;IACD,cAAc,EAEZ,SAAS,eACV;IACD,KAAK,EAEH,YAAY,CAAC,2BAA2B,EACzC;IACF,CAAC;;EAGJ,iBAAiB,OAAO,WAAW;AACjC,OAAI,OAAO,OAAO,SAAS,cAEzB,OAAM,EAAE,eAAe,gBAAgB,CAAC;;EAG7C,CACF;AAED,SAAQ,KAAK,qBAAqB,eAAe,CAAC;AAGlD,SAAQ,KAAK,iBAAiB,eAAe,CAAC;AAE9C,QAAO;;;;;;;;;;;;AAaT,MAAa,WAAW;;;;;;;;;;;;;AAaxB,MAAa,iBAAiB"}
@@ -1,2 +1,284 @@
1
- import{getConfiguration as e}from"@intlayer/config/node";import{ROUTING_MODE as t}from"@intlayer/config/defaultValues";import{parse as n}from"node:url";import{getCanonicalPath as r,getLocalizedPath as i,getRewriteRules as a,localeDetector as o}from"@intlayer/core/localization";import{getCookie as s,getLocaleFromStorage as c,setLocaleInStorage as l}from"@intlayer/core/utils";const u=(u,d)=>{let{internationalization:f,routing:p}=e(u),{locales:m,defaultLocale:h}=f,{basePath:g=``,mode:_=t,rewrite:v}=p,y=new Map,b=_===`no-prefix`||_===`search-params`,x=_===`prefix-all`,S=a(v,`url`),C=e=>c({getCookie:t=>s(t,e.headers.cookie)}),w=(e,t)=>{if(_!==`search-params`)return e;let n=new URLSearchParams(e??``);return n.set(`locale`,t),`?${n.toString()}`},T=e=>{let t=e.split(`/`).filter(Boolean)[0];if(t&&m.includes(t))return t},E=(e,t,n,r)=>{if(r){let i=(y.get(r)||0)+1;if(y.set(r,i),i>10)return console.error(`[REDIRECT LOOP DETECTED!]`,{originalUrl:r,redirectCount:i,lastRedirectTo:t,reason:n}),e.writeHead(500,{"Content-Type":`text/plain`}),e.end(`Redirect loop detected: ${i} redirects from ${r}`)}return e.writeHead(301,{Location:t}),e.end()},D=(e,t,n,r)=>{e.url!==n&&(e.url=n),r&&l(r,{setHeader:(n,r)=>{t.setHeader(n,r),e.headers[n]=r}})},O=(e,t,n)=>{let r=t.startsWith(`/${e}`)?t.slice(`/${e}`.length):t,i=g.startsWith(`/`)?g:`/${g}`,a=i.endsWith(`/`)?i.slice(0,-1):i;if(_===`no-prefix`||_===`search-params`)return n?`${r||`/`}${n}`:r||`/`;let o=`${a}${t.startsWith(`/${e}`)?t:`/${e}${t===`/`?``:t}`}`;return!x&&e===h&&(o=`${a}${r||`/`}`),n&&(o+=n),o},k=({req:e,res:t,next:n,originalPath:i,searchParams:a,storageLocale:s,originalUrl:c})=>{let l=T(i),u=s??h;if(s||(u=o(e.headers,m,h)),l){let e=r(i.slice(`/${l}`.length)||`/`,l,S),n=w(a,l);return E(t,n?`${e}${n}`:`${e}${a??``}`,void 0,c)}let d=r(i,u,S);if(_===`search-params`){if(new URLSearchParams(a??``).get(`locale`)===u)return D(e,t,`${`/${u}${d===`/`?``:d}`}${a??``}`,u),n();let r=w(a,u);return E(t,r?`${i}${r}`:`${i}${a??``}`,void 0,c)}let f=`/${u}${d===`/`?``:d}`,p=w(a,u);return D(e,t,p?`${f}${p}`:`${f}${a??``}`,u),n()},A=({req:e,res:t,next:n,originalPath:r,searchParams:i,pathLocale:a,storageLocale:o,originalUrl:s})=>{if(!a){j({req:e,res:t,next:n,originalPath:r,searchParams:i,storageLocale:o,originalUrl:s});return}M({req:e,res:t,next:n,originalPath:r,searchParams:i,pathLocale:a,originalUrl:s})},j=({req:e,res:t,next:n,originalPath:a,searchParams:s,storageLocale:c,originalUrl:l})=>{let u=c??o(e.headers,m,h);m.includes(u)||(u=h);let d=r(a,u,S),f=i(d,u,S),p=typeof f==`string`?f:f.path,g=w(s,u),_=O(u,p,g);return x||u!==h?E(t,_,void 0,l):(D(e,t,`/${u}${d===`/`?``:d}${s}`,u),n())},M=({req:e,res:t,next:n,originalPath:i,searchParams:a,pathLocale:o,originalUrl:s})=>{let c=i.slice(`/${o}`.length),l=r(c,o,S);if(l!==c){let e=`/${o}${l===`/`?``:l}`;return E(t,a?`${e}${a}`:e,void 0,s)}N({req:e,res:t,next:n,searchParams:a,pathLocale:o,canonicalPath:l,originalUrl:s})},N=({req:e,res:t,next:n,searchParams:r,pathLocale:a,canonicalPath:o,originalUrl:s})=>{if(!x&&a===h){let e=i(o,a,S),n=typeof e==`string`?e:e.path,c=g.startsWith(`/`)?g:`/${g}`,l=c.endsWith(`/`)?c.slice(0,-1):c,u=n;return u.startsWith(`/`)&&(u=u.slice(1)),E(t,`${l}/${u}`.replace(/\/+/g,`/`)+(r??``),void 0,s)}let c=`/${a}${o===`/`?``:o}`;return D(e,t,r?`${c}${r}`:c,a),n()};return{name:`vite-intlayer-middleware-plugin`,configureServer:e=>{e.middlewares.use((e,t,r)=>{if((d?.ignore?.(e)??!1)||e.url?.startsWith(`/node_modules`)||e.url?.startsWith(`/@`)||e.url?.startsWith(`/_`)||e.url?.split(`?`)[0].match(/\.[a-z]+$/i))return r();let i=n(e.url??`/`,!0),a=i.pathname??`/`,o=i.search??``,s=T(a),c=C(e),l=s&&m.includes(s)?s:c,u=e.url;if(b){k({req:e,res:t,next:r,originalPath:a,searchParams:o,storageLocale:l,originalUrl:u});return}A({req:e,res:t,next:r,originalPath:a,searchParams:o,pathLocale:s,storageLocale:l,originalUrl:u})}),y.size>100&&y.clear()}}},d=u,f=u;export{f as intLayerMiddlewarePlugin,d as intlayerMiddleware,u as intlayerProxy};
1
+ import { getConfiguration } from "@intlayer/config/node";
2
+ import { ROUTING_MODE } from "@intlayer/config/defaultValues";
3
+ import { parse } from "node:url";
4
+ import { getCanonicalPath, getLocalizedPath, getRewriteRules, localeDetector } from "@intlayer/core/localization";
5
+ import { getCookie, getLocaleFromStorage, setLocaleInStorage } from "@intlayer/core/utils";
6
+
7
+ //#region src/intlayerProxyPlugin.ts
8
+ /**
9
+ * Vite plugin that provides a development middleware for locale-based routing.
10
+ *
11
+ * This plugin mimics the behavior of the Intlayer middleware in Next.js,
12
+ * handling locale detection, redirects, and rewrites during development.
13
+ *
14
+ * @param configOptions - Optional configuration for Intlayer.
15
+ * @param options - Plugin-specific options, like ignoring certain paths.
16
+ * @returns A Vite plugin.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { intlayerProxy } from 'vite-intlayer';
21
+ *
22
+ * export default defineConfig({
23
+ * plugins: [intlayerProxy()],
24
+ * });
25
+ * ```
26
+ */
27
+ const intlayerProxy = (configOptions, options) => {
28
+ const { internationalization, routing } = getConfiguration(configOptions);
29
+ const { locales: supportedLocales, defaultLocale } = internationalization;
30
+ const { basePath = "", mode = ROUTING_MODE, rewrite } = routing;
31
+ const redirectCounts = /* @__PURE__ */ new Map();
32
+ const MAX_REDIRECTS = 10;
33
+ const noPrefix = mode === "no-prefix" || mode === "search-params";
34
+ const prefixDefault = mode === "prefix-all";
35
+ const rewriteRules = getRewriteRules(rewrite, "url");
36
+ /**
37
+ * Retrieves the locale from storage (cookies, localStorage, sessionStorage).
38
+ */
39
+ const getStorageLocale = (req) => {
40
+ return getLocaleFromStorage({ getCookie: (name) => getCookie(name, req.headers.cookie) });
41
+ };
42
+ /**
43
+ * Appends locale to search params when routing mode is 'search-params'.
44
+ */
45
+ const appendLocaleSearchIfNeeded = (search, locale) => {
46
+ if (mode !== "search-params") return search;
47
+ const params = new URLSearchParams(search ?? "");
48
+ params.set("locale", locale);
49
+ return `?${params.toString()}`;
50
+ };
51
+ /**
52
+ * Extracts the locale from the URL pathname if present as the first segment.
53
+ */
54
+ const getPathLocale = (pathname) => {
55
+ const firstSegment = pathname.split("/").filter(Boolean)[0];
56
+ if (firstSegment && supportedLocales.includes(firstSegment)) return firstSegment;
57
+ };
58
+ /**
59
+ * Writes a 301 redirect response with the given new URL.
60
+ */
61
+ const redirectUrl = (res, newUrl, reason, originalUrl) => {
62
+ if (originalUrl) {
63
+ const count = (redirectCounts.get(originalUrl) || 0) + 1;
64
+ redirectCounts.set(originalUrl, count);
65
+ if (count > MAX_REDIRECTS) {
66
+ console.error("[REDIRECT LOOP DETECTED!]", {
67
+ originalUrl,
68
+ redirectCount: count,
69
+ lastRedirectTo: newUrl,
70
+ reason
71
+ });
72
+ res.writeHead(500, { "Content-Type": "text/plain" });
73
+ return res.end(`Redirect loop detected: ${count} redirects from ${originalUrl}`);
74
+ }
75
+ }
76
+ res.writeHead(301, { Location: newUrl });
77
+ return res.end();
78
+ };
79
+ /**
80
+ * "Rewrite" the request internally by adjusting req.url;
81
+ * we also set the locale in the response header if needed.
82
+ */
83
+ const rewriteUrl = (req, res, newUrl, locale) => {
84
+ if (req.url !== newUrl) req.url = newUrl;
85
+ if (locale) setLocaleInStorage(locale, { setHeader: (name, value) => {
86
+ res.setHeader(name, value);
87
+ req.headers[name] = value;
88
+ } });
89
+ };
90
+ /**
91
+ * Constructs a new path string, optionally including a locale prefix, basePath, and search parameters.
92
+ * - basePath: (e.g., '/myapp')
93
+ * - locale: (e.g., 'en')
94
+ * - currentPath:(e.g., '/products/shoes')
95
+ * - search: (e.g., '?foo=bar')
96
+ */
97
+ const constructPath = (locale, currentPath, search) => {
98
+ const pathWithoutPrefix = currentPath.startsWith(`/${locale}`) ? currentPath.slice(`/${locale}`.length) : currentPath;
99
+ const cleanBasePath = basePath.startsWith("/") ? basePath : `/${basePath}`;
100
+ const normalizedBasePath = cleanBasePath.endsWith("/") ? cleanBasePath.slice(0, -1) : cleanBasePath;
101
+ if (mode === "no-prefix" || mode === "search-params") return search ? `${pathWithoutPrefix || "/"}${search}` : pathWithoutPrefix || "/";
102
+ let newPath = `${normalizedBasePath}${currentPath.startsWith(`/${locale}`) ? currentPath : `/${locale}${currentPath === "/" ? "" : currentPath}`}`;
103
+ if (!prefixDefault && locale === defaultLocale) newPath = `${normalizedBasePath}${pathWithoutPrefix || "/"}`;
104
+ if (search) newPath += search;
105
+ return newPath;
106
+ };
107
+ /**
108
+ * If `noPrefix` is true, we never prefix the locale in the URL.
109
+ * We simply rewrite the request to the same path, but with the best-chosen locale
110
+ * in a header or search params if desired.
111
+ */
112
+ const handleNoPrefix = ({ req, res, next, originalPath, searchParams, storageLocale, originalUrl }) => {
113
+ const pathLocale = getPathLocale(originalPath);
114
+ let locale = storageLocale ?? defaultLocale;
115
+ if (!storageLocale) locale = localeDetector(req.headers, supportedLocales, defaultLocale);
116
+ if (pathLocale) {
117
+ const canonicalPath = getCanonicalPath(originalPath.slice(`/${pathLocale}`.length) || "/", pathLocale, rewriteRules);
118
+ const search = appendLocaleSearchIfNeeded(searchParams, pathLocale);
119
+ return redirectUrl(res, search ? `${canonicalPath}${search}` : `${canonicalPath}${searchParams ?? ""}`, void 0, originalUrl);
120
+ }
121
+ const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);
122
+ if (mode === "search-params") {
123
+ if (new URLSearchParams(searchParams ?? "").get("locale") === locale) {
124
+ rewriteUrl(req, res, `${`/${locale}${canonicalPath === "/" ? "" : canonicalPath}`}${searchParams ?? ""}`, locale);
125
+ return next();
126
+ }
127
+ const search = appendLocaleSearchIfNeeded(searchParams, locale);
128
+ return redirectUrl(res, search ? `${originalPath}${search}` : `${originalPath}${searchParams ?? ""}`, void 0, originalUrl);
129
+ }
130
+ const internalPath = `/${locale}${canonicalPath === "/" ? "" : canonicalPath}`;
131
+ const search = appendLocaleSearchIfNeeded(searchParams, locale);
132
+ rewriteUrl(req, res, search ? `${internalPath}${search}` : `${internalPath}${searchParams ?? ""}`, locale);
133
+ return next();
134
+ };
135
+ /**
136
+ * The main prefix logic:
137
+ * - If there's no pathLocale in the URL, we might want to detect & redirect or rewrite
138
+ * - If there is a pathLocale, handle storage mismatch or default locale special cases
139
+ */
140
+ const handlePrefix = ({ req, res, next, originalPath, searchParams, pathLocale, storageLocale, originalUrl }) => {
141
+ if (!pathLocale) {
142
+ handleMissingPathLocale({
143
+ req,
144
+ res,
145
+ next,
146
+ originalPath,
147
+ searchParams,
148
+ storageLocale,
149
+ originalUrl
150
+ });
151
+ return;
152
+ }
153
+ handleExistingPathLocale({
154
+ req,
155
+ res,
156
+ next,
157
+ originalPath,
158
+ searchParams,
159
+ pathLocale,
160
+ originalUrl
161
+ });
162
+ };
163
+ /**
164
+ * Handles requests where the locale is missing from the URL pathname.
165
+ * We detect a locale from storage / headers / default, then either redirect or rewrite.
166
+ */
167
+ const handleMissingPathLocale = ({ req, res, next, originalPath, searchParams, storageLocale, originalUrl }) => {
168
+ let locale = storageLocale ?? localeDetector(req.headers, supportedLocales, defaultLocale);
169
+ if (!supportedLocales.includes(locale)) locale = defaultLocale;
170
+ const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);
171
+ const targetLocalizedPathResult = getLocalizedPath(canonicalPath, locale, rewriteRules);
172
+ const targetLocalizedPath = typeof targetLocalizedPathResult === "string" ? targetLocalizedPathResult : targetLocalizedPathResult.path;
173
+ const search = appendLocaleSearchIfNeeded(searchParams, locale);
174
+ const newPath = constructPath(locale, targetLocalizedPath, search);
175
+ if (prefixDefault || locale !== defaultLocale) return redirectUrl(res, newPath, void 0, originalUrl);
176
+ rewriteUrl(req, res, `/${locale}${canonicalPath === "/" ? "" : canonicalPath}${searchParams}`, locale);
177
+ return next();
178
+ };
179
+ /**
180
+ * Handles requests where the locale prefix is present in the pathname.
181
+ */
182
+ const handleExistingPathLocale = ({ req, res, next, originalPath, searchParams, pathLocale, originalUrl }) => {
183
+ const rawPath = originalPath.slice(`/${pathLocale}`.length);
184
+ const canonicalPath = getCanonicalPath(rawPath, pathLocale, rewriteRules);
185
+ if (canonicalPath !== rawPath) {
186
+ const internalUrl = `/${pathLocale}${canonicalPath === "/" ? "" : canonicalPath}`;
187
+ return redirectUrl(res, searchParams ? `${internalUrl}${searchParams}` : internalUrl, void 0, originalUrl);
188
+ }
189
+ handleDefaultLocaleRedirect({
190
+ req,
191
+ res,
192
+ next,
193
+ searchParams,
194
+ pathLocale,
195
+ canonicalPath,
196
+ originalUrl
197
+ });
198
+ };
199
+ /**
200
+ * If the path locale is the default locale but we don't want to prefix the default, remove it.
201
+ */
202
+ const handleDefaultLocaleRedirect = ({ req, res, next, searchParams, pathLocale, canonicalPath, originalUrl }) => {
203
+ if (!prefixDefault && pathLocale === defaultLocale) {
204
+ const targetLocalizedPathResult = getLocalizedPath(canonicalPath, pathLocale, rewriteRules);
205
+ const targetLocalizedPath = typeof targetLocalizedPathResult === "string" ? targetLocalizedPathResult : targetLocalizedPathResult.path;
206
+ const cleanBasePath = basePath.startsWith("/") ? basePath : `/${basePath}`;
207
+ const normalizedBasePath = cleanBasePath.endsWith("/") ? cleanBasePath.slice(0, -1) : cleanBasePath;
208
+ let finalPath = targetLocalizedPath;
209
+ if (finalPath.startsWith("/")) finalPath = finalPath.slice(1);
210
+ return redirectUrl(res, `${normalizedBasePath}/${finalPath}`.replace(/\/+/g, "/") + (searchParams ?? ""), void 0, originalUrl);
211
+ }
212
+ const internalUrl = `/${pathLocale}${canonicalPath === "/" ? "" : canonicalPath}`;
213
+ rewriteUrl(req, res, searchParams ? `${internalUrl}${searchParams}` : internalUrl, pathLocale);
214
+ return next();
215
+ };
216
+ return {
217
+ name: "vite-intlayer-middleware-plugin",
218
+ configureServer: (server) => {
219
+ server.middlewares.use((req, res, next) => {
220
+ if ((options?.ignore?.(req) ?? false) || req.url?.startsWith("/node_modules") || req.url?.startsWith("/@") || req.url?.startsWith("/_") || req.url?.split("?")[0].match(/\.[a-z]+$/i)) return next();
221
+ const parsedUrl = parse(req.url ?? "/", true);
222
+ const originalPath = parsedUrl.pathname ?? "/";
223
+ const searchParams = parsedUrl.search ?? "";
224
+ const pathLocale = getPathLocale(originalPath);
225
+ const storageLocale = getStorageLocale(req);
226
+ const effectiveStorageLocale = pathLocale && supportedLocales.includes(pathLocale) ? pathLocale : storageLocale;
227
+ const originalUrl = req.url;
228
+ if (noPrefix) {
229
+ handleNoPrefix({
230
+ req,
231
+ res,
232
+ next,
233
+ originalPath,
234
+ searchParams,
235
+ storageLocale: effectiveStorageLocale,
236
+ originalUrl
237
+ });
238
+ return;
239
+ }
240
+ handlePrefix({
241
+ req,
242
+ res,
243
+ next,
244
+ originalPath,
245
+ searchParams,
246
+ pathLocale,
247
+ storageLocale: effectiveStorageLocale,
248
+ originalUrl
249
+ });
250
+ });
251
+ if (redirectCounts.size > 100) redirectCounts.clear();
252
+ }
253
+ };
254
+ };
255
+ /**
256
+ * @deprecated Rename to intlayerProxy instead
257
+ *
258
+ * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.
259
+ *
260
+ * ```ts
261
+ * // Example usage of the plugin in a Vite configuration
262
+ * export default defineConfig({
263
+ * plugins: [ intlayerMiddleware() ],
264
+ * });
265
+ * ```
266
+ */
267
+ const intlayerMiddleware = intlayerProxy;
268
+ /**
269
+ * @deprecated Rename to intlayerProxy instead
270
+ *
271
+ * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.
272
+
273
+ * ```ts
274
+ * // Example usage of the plugin in a Vite configuration
275
+ * export default defineConfig({
276
+ * plugins: [ intlayerMiddleware() ],
277
+ * });
278
+ * ```
279
+ */
280
+ const intLayerMiddlewarePlugin = intlayerProxy;
281
+
282
+ //#endregion
283
+ export { intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy };
2
284
  //# sourceMappingURL=intlayerProxyPlugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerProxyPlugin.mjs","names":[],"sources":["../../src/intlayerProxyPlugin.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { parse } from 'node:url';\nimport { ROUTING_MODE } from '@intlayer/config/defaultValues';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getCanonicalPath,\n getLocalizedPath,\n getRewriteRules,\n localeDetector,\n} from '@intlayer/core/localization';\nimport {\n getCookie,\n getLocaleFromStorage,\n setLocaleInStorage,\n} from '@intlayer/core/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\n/* @ts-ignore - Vite types error */\nimport type { Connect, Plugin } from 'vite';\n\ntype IntlayerProxyPluginOptions = {\n /**\n * A function that allows you to ignore specific requests from the intlayer proxy.\n *\n * @example\n * ```ts\n * export default defineConfig({\n * plugins: [ intlayerProxyPlugin({ ignore: (req) => req.url?.startsWith('/api') }) ],\n * });\n * ```\n *\n * @param req - The incoming request.\n * @returns A boolean value indicating whether to ignore the request.\n */\n ignore?: (req: IncomingMessage) => boolean | undefined;\n};\n\n/**\n * Vite plugin that provides a development middleware for locale-based routing.\n *\n * This plugin mimics the behavior of the Intlayer middleware in Next.js,\n * handling locale detection, redirects, and rewrites during development.\n *\n * @param configOptions - Optional configuration for Intlayer.\n * @param options - Plugin-specific options, like ignoring certain paths.\n * @returns A Vite plugin.\n *\n * @example\n * ```ts\n * import { intlayerProxy } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerProxy()],\n * });\n * ```\n */\nexport const intlayerProxy = (\n configOptions?: GetConfigurationOptions,\n options?: IntlayerProxyPluginOptions\n): Plugin => {\n const intlayerConfig = getConfiguration(configOptions);\n\n const { internationalization, routing } = intlayerConfig;\n const { locales: supportedLocales, defaultLocale } = internationalization;\n\n const { basePath = '', mode = ROUTING_MODE, rewrite } = routing;\n\n // Track redirect counts per request to detect loops\n const redirectCounts = new Map<string, number>();\n const MAX_REDIRECTS = 10;\n\n // Derived flags from routing.mode\n const noPrefix = mode === 'no-prefix' || mode === 'search-params';\n const prefixDefault = mode === 'prefix-all';\n\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n /* --------------------------------------------------------------------\n * Helper & Utility Functions\n * --------------------------------------------------------------------\n */\n\n /**\n * Retrieves the locale from storage (cookies, localStorage, sessionStorage).\n */\n const getStorageLocale = (req: IncomingMessage): Locale | undefined => {\n const locale = getLocaleFromStorage({\n getCookie: (name: string) => getCookie(name, req.headers.cookie),\n });\n return locale;\n };\n\n /**\n * Appends locale to search params when routing mode is 'search-params'.\n */\n const appendLocaleSearchIfNeeded = (\n search: string | undefined,\n locale: Locale\n ): string | undefined => {\n if (mode !== 'search-params') return search;\n\n const params = new URLSearchParams(search ?? '');\n\n params.set('locale', locale);\n\n return `?${params.toString()}`;\n };\n\n /**\n * Extracts the locale from the URL pathname if present as the first segment.\n */\n const getPathLocale = (pathname: string): Locale | undefined => {\n // e.g. if pathname is /en/some/page or /en\n // we check if \"en\" is in your supportedLocales\n const segments = pathname.split('/').filter(Boolean);\n const firstSegment = segments[0];\n if (firstSegment && supportedLocales.includes(firstSegment as Locale)) {\n return firstSegment as Locale;\n }\n return undefined;\n };\n\n /**\n * Writes a 301 redirect response with the given new URL.\n */\n const redirectUrl = (\n res: ServerResponse<IncomingMessage>,\n newUrl: string,\n reason?: string,\n originalUrl?: string\n ) => {\n // Track redirect count to detect loops\n if (originalUrl) {\n const count = (redirectCounts.get(originalUrl) || 0) + 1;\n redirectCounts.set(originalUrl, count);\n\n if (count > MAX_REDIRECTS) {\n console.error('[REDIRECT LOOP DETECTED!]', {\n originalUrl,\n redirectCount: count,\n lastRedirectTo: newUrl,\n reason,\n });\n res.writeHead(500, { 'Content-Type': 'text/plain' });\n return res.end(\n `Redirect loop detected: ${count} redirects from ${originalUrl}`\n );\n }\n }\n\n res.writeHead(301, { Location: newUrl });\n return res.end();\n };\n\n /**\n * \"Rewrite\" the request internally by adjusting req.url;\n * we also set the locale in the response header if needed.\n */\n const rewriteUrl = (\n req: Connect.IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n newUrl: string,\n locale?: Locale\n ) => {\n if (req.url !== newUrl) {\n req.url = newUrl;\n }\n // If you want to mimic Next.js's behavior of setting a header for the locale:\n if (locale) {\n setLocaleInStorage(locale, {\n setHeader: (name: string, value: string) => {\n res.setHeader(name, value);\n req.headers[name] = value;\n },\n });\n }\n };\n\n /**\n * Constructs a new path string, optionally including a locale prefix, basePath, and search parameters.\n * - basePath: (e.g., '/myapp')\n * - locale: (e.g., 'en')\n * - currentPath:(e.g., '/products/shoes')\n * - search: (e.g., '?foo=bar')\n */\n const constructPath = (\n locale: Locale,\n currentPath: string,\n search?: string\n ) => {\n // Strip any incoming locale prefix if present\n const pathWithoutPrefix = currentPath.startsWith(`/${locale}`)\n ? currentPath.slice(`/${locale}`.length)\n : currentPath;\n\n // Ensure basePath always starts with '/', and remove trailing slash if needed\n const cleanBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;\n const normalizedBasePath = cleanBasePath.endsWith('/')\n ? cleanBasePath.slice(0, -1)\n : cleanBasePath;\n\n // In 'search-params' and 'no-prefix' modes, do not prefix the path with the locale\n if (mode === 'no-prefix' || mode === 'search-params') {\n const newPath = search\n ? `${pathWithoutPrefix || '/'}${search}`\n : pathWithoutPrefix || '/';\n return newPath;\n }\n\n // Check if path already starts with locale to avoid double-prefixing\n const pathWithLocalePrefix = currentPath.startsWith(`/${locale}`)\n ? currentPath\n : `/${locale}${currentPath === '/' ? '' : currentPath}`;\n\n let newPath = `${normalizedBasePath}${pathWithLocalePrefix}`;\n\n // Special case: if prefixDefault is false and locale is defaultLocale, remove the locale prefix\n if (!prefixDefault && locale === defaultLocale) {\n newPath = `${normalizedBasePath}${pathWithoutPrefix || '/'}`;\n }\n\n // Append search parameters if provided\n if (search) {\n newPath += search;\n }\n\n return newPath;\n };\n\n /* --------------------------------------------------------------------\n * Handlers that mirror Next.js style logic\n * --------------------------------------------------------------------\n */\n\n /**\n * If `noPrefix` is true, we never prefix the locale in the URL.\n * We simply rewrite the request to the same path, but with the best-chosen locale\n * in a header or search params if desired.\n */\n const handleNoPrefix = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n const pathLocale = getPathLocale(originalPath);\n // Determine the best locale\n let locale = storageLocale ?? defaultLocale;\n\n // Use fallback to localeDetector if no storage locale\n if (!storageLocale) {\n const detectedLocale = localeDetector(\n req.headers as Record<string, string>,\n supportedLocales,\n defaultLocale\n );\n locale = detectedLocale as Locale;\n }\n\n if (pathLocale) {\n const pathWithoutLocale =\n originalPath.slice(`/${pathLocale}`.length) || '/';\n\n const canonicalPath = getCanonicalPath(\n pathWithoutLocale,\n pathLocale,\n rewriteRules\n );\n\n const search = appendLocaleSearchIfNeeded(searchParams, pathLocale);\n\n const redirectPath = search\n ? `${canonicalPath}${search}`\n : `${canonicalPath}${searchParams ?? ''}`;\n\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);\n\n // In search-params mode, we need to redirect to add the locale search param\n if (mode === 'search-params') {\n // Check if locale search param already exists and matches the detected locale\n const existingSearchParams = new URLSearchParams(searchParams ?? '');\n const existingLocale = existingSearchParams.get('locale');\n\n // If the existing locale matches the detected locale, no redirect needed\n if (existingLocale === locale) {\n // For internal routing, we need to add the locale prefix so the framework can match [locale] param\n const internalPath = `/${locale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const rewritePath = `${internalPath}${searchParams ?? ''}`;\n\n // Rewrite internally (URL stays the same in browser, but internally routes to /[locale]/path)\n rewriteUrl(req, res, rewritePath, locale);\n return next();\n }\n\n // Locale param missing or doesn't match - redirect to add/update it\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const redirectPath = search\n ? `${originalPath}${search}`\n : `${originalPath}${searchParams ?? ''}`;\n\n // Redirect to add/update the locale search param (URL changes in browser)\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n // For no-prefix mode (not search-params), add locale prefix internally for routing\n const internalPath = `/${locale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n\n // Add search params if needed\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const rewritePath = search\n ? `${internalPath}${search}`\n : `${internalPath}${searchParams ?? ''}`;\n\n // Rewrite internally (URL stays the same in browser, but internally routes to /[locale]/path)\n rewriteUrl(req, res, rewritePath, locale);\n\n return next();\n };\n\n /**\n * The main prefix logic:\n * - If there's no pathLocale in the URL, we might want to detect & redirect or rewrite\n * - If there is a pathLocale, handle storage mismatch or default locale special cases\n */\n const handlePrefix = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n pathLocale?: Locale;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n // 1. If pathLocale is missing, handle\n if (!pathLocale) {\n handleMissingPathLocale({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n });\n return;\n }\n\n // 2. If pathLocale exists, handle it\n handleExistingPathLocale({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n originalUrl,\n });\n };\n\n /**\n * Handles requests where the locale is missing from the URL pathname.\n * We detect a locale from storage / headers / default, then either redirect or rewrite.\n */\n const handleMissingPathLocale = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n // 1. Choose the best locale\n let locale = (storageLocale ??\n localeDetector(\n req.headers as Record<string, string>,\n supportedLocales,\n defaultLocale\n )) as Locale;\n\n // If still invalid, fallback\n if (!supportedLocales.includes(locale)) {\n locale = defaultLocale;\n }\n\n // Resolve to canonical path.\n // If user visits /a-propos (implied 'fr'), we resolve to /about\n const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);\n\n // Determine target localized path for redirection\n // /about + 'fr' -> /a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n locale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n // Construct new path - preserving original search params\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const newPath = constructPath(locale, targetLocalizedPath, search);\n\n // If we always prefix default or if this is not the default locale, do a 301 redirect\n // so that the user sees the locale in the URL.\n if (prefixDefault || locale !== defaultLocale) {\n return redirectUrl(res, newPath, undefined, originalUrl);\n }\n\n // If we do NOT prefix the default locale, just rewrite in place using canonical path for framework matching.\n // searchParams MUST be preserved here — dropping them causes the framework (e.g. TanStack Start) to\n // see a URL with no search params, trigger a validateSearch normalisation redirect to the prefixed URL\n // (e.g. /en?page=1&...), which the middleware then strips back to /?..., creating an infinite loop.\n rewriteUrl(\n req,\n res,\n `/${locale}${canonicalPath === '/' ? '' : canonicalPath}${searchParams}`,\n locale\n );\n return next();\n };\n\n /**\n * Handles requests where the locale prefix is present in the pathname.\n */\n const handleExistingPathLocale = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n pathLocale: Locale;\n originalUrl?: string;\n }) => {\n const rawPath = originalPath.slice(`/${pathLocale}`.length);\n\n // Identify the Canonical Path (Internal path)\n // Ex: /a-propos (from URL) -> /about (Canonical)\n const canonicalPath = getCanonicalPath(rawPath, pathLocale, rewriteRules);\n\n // Redirect Pretty URL to Canonical URL (Vite Specific Requirement)\n // If request is /fr/a-props (Pretty) -> Redirect to /fr/about (Canonical)\n // This allows the SPA router to handle the route naturally.\n if (canonicalPath !== rawPath) {\n const internalUrl = `/${pathLocale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const redirectPath = searchParams\n ? `${internalUrl}${searchParams}`\n : internalUrl;\n\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n // In prefix modes, respect the URL path locale\n // The path locale takes precedence, and we'll update storage to match\n handleDefaultLocaleRedirect({\n req,\n res,\n next,\n searchParams,\n pathLocale,\n canonicalPath,\n originalUrl,\n });\n };\n\n /**\n * If the path locale is the default locale but we don't want to prefix the default, remove it.\n */\n const handleDefaultLocaleRedirect = ({\n req,\n res,\n next,\n searchParams,\n pathLocale,\n canonicalPath,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n searchParams: string;\n pathLocale: Locale;\n canonicalPath: string;\n originalUrl?: string;\n }) => {\n // If we don't prefix default AND the path locale is the default locale -> remove it\n if (!prefixDefault && pathLocale === defaultLocale) {\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n // Construct path without prefix\n const cleanBasePath = basePath.startsWith('/')\n ? basePath\n : `/${basePath}`;\n const normalizedBasePath = cleanBasePath.endsWith('/')\n ? cleanBasePath.slice(0, -1)\n : cleanBasePath;\n\n let finalPath = targetLocalizedPath;\n if (finalPath.startsWith('/')) finalPath = finalPath.slice(1);\n\n const fullPath = `${normalizedBasePath}/${finalPath}`.replace(\n /\\/+/g,\n '/'\n );\n\n return redirectUrl(\n res,\n fullPath + (searchParams ?? ''),\n undefined,\n originalUrl\n );\n }\n\n // If we do prefix default or pathLocale != default, keep as is, but rewrite to canonical internally\n const internalUrl = `/${pathLocale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const newPath = searchParams\n ? `${internalUrl}${searchParams}`\n : internalUrl;\n\n rewriteUrl(req, res, newPath, pathLocale);\n return next();\n };\n\n return {\n name: 'vite-intlayer-middleware-plugin',\n configureServer: (server) => {\n server.middlewares.use((req, res, next) => {\n // Bypass assets and special Vite endpoints\n if (\n // Custom ignore function\n (options?.ignore?.(req) ?? false) ||\n req.url?.startsWith('/node_modules') ||\n /**\n * /^@vite/ # HMR client and helpers\n * /^@fs/ # file-system import serving\n * /^@id/ # virtual module ids\n * /^@tanstack/start-router-manifest # Tanstack Start Router manifest\n */\n req.url?.startsWith('/@') ||\n /**\n * /^__vite_ping$ # health ping\n * /^__open-in-editor$\n * /^__manifest$ # Remix/RR7 lazyRouteDiscovery\n */\n req.url?.startsWith('/_') ||\n /**\n * ./myFile.js\n */\n req.url?.split('?')[0].match(/\\.[a-z]+$/i) // checks for file extensions\n ) {\n return next();\n }\n\n // Parse original URL for path and query\n const parsedUrl = parse(req.url ?? '/', true);\n const originalPath = parsedUrl.pathname ?? '/';\n const searchParams = parsedUrl.search ?? '';\n\n // Check if there's a locale prefix in the path FIRST\n const pathLocale = getPathLocale(originalPath);\n\n // Attempt to read the locale from storage (cookies, localStorage, etc.)\n const storageLocale = getStorageLocale(req);\n\n // CRITICAL FIX: If there's a valid pathLocale, it takes precedence over storage\n // This prevents race conditions when cookies are stale during locale switches\n const effectiveStorageLocale =\n pathLocale && supportedLocales.includes(pathLocale)\n ? pathLocale\n : storageLocale;\n\n // Store original URL for redirect tracking\n const originalUrl = req.url;\n\n // If noPrefix is true, we skip prefix logic altogether\n if (noPrefix) {\n handleNoPrefix({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale: effectiveStorageLocale,\n originalUrl,\n });\n return;\n }\n\n // Otherwise, handle prefix logic\n handlePrefix({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n storageLocale: effectiveStorageLocale,\n originalUrl,\n });\n });\n\n // Clean up redirect counts periodically (every 100 requests)\n if (redirectCounts.size > 100) {\n redirectCounts.clear();\n }\n },\n };\n};\n\n/**\n * @deprecated Rename to intlayerProxy instead\n *\n * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayerMiddleware() ],\n * });\n * ```\n */\nexport const intlayerMiddleware = intlayerProxy;\n\n/**\n * @deprecated Rename to intlayerProxy instead\n * \n * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.\n\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayerMiddleware() ],\n * });\n * ```\n */\nexport const intLayerMiddlewarePlugin = intlayerProxy;\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerProxyPlugin.mjs","names":[],"sources":["../../src/intlayerProxyPlugin.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { parse } from 'node:url';\nimport { ROUTING_MODE } from '@intlayer/config/defaultValues';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getCanonicalPath,\n getLocalizedPath,\n getRewriteRules,\n localeDetector,\n} from '@intlayer/core/localization';\nimport {\n getCookie,\n getLocaleFromStorage,\n setLocaleInStorage,\n} from '@intlayer/core/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\n/* @ts-ignore - Vite types error */\nimport type { Connect, Plugin } from 'vite';\n\ntype IntlayerProxyPluginOptions = {\n /**\n * A function that allows you to ignore specific requests from the intlayer proxy.\n *\n * @example\n * ```ts\n * export default defineConfig({\n * plugins: [ intlayerProxyPlugin({ ignore: (req) => req.url?.startsWith('/api') }) ],\n * });\n * ```\n *\n * @param req - The incoming request.\n * @returns A boolean value indicating whether to ignore the request.\n */\n ignore?: (req: IncomingMessage) => boolean | undefined;\n};\n\n/**\n * Vite plugin that provides a development middleware for locale-based routing.\n *\n * This plugin mimics the behavior of the Intlayer middleware in Next.js,\n * handling locale detection, redirects, and rewrites during development.\n *\n * @param configOptions - Optional configuration for Intlayer.\n * @param options - Plugin-specific options, like ignoring certain paths.\n * @returns A Vite plugin.\n *\n * @example\n * ```ts\n * import { intlayerProxy } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerProxy()],\n * });\n * ```\n */\nexport const intlayerProxy = (\n configOptions?: GetConfigurationOptions,\n options?: IntlayerProxyPluginOptions\n): Plugin => {\n const intlayerConfig = getConfiguration(configOptions);\n\n const { internationalization, routing } = intlayerConfig;\n const { locales: supportedLocales, defaultLocale } = internationalization;\n\n const { basePath = '', mode = ROUTING_MODE, rewrite } = routing;\n\n // Track redirect counts per request to detect loops\n const redirectCounts = new Map<string, number>();\n const MAX_REDIRECTS = 10;\n\n // Derived flags from routing.mode\n const noPrefix = mode === 'no-prefix' || mode === 'search-params';\n const prefixDefault = mode === 'prefix-all';\n\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n /* --------------------------------------------------------------------\n * Helper & Utility Functions\n * --------------------------------------------------------------------\n */\n\n /**\n * Retrieves the locale from storage (cookies, localStorage, sessionStorage).\n */\n const getStorageLocale = (req: IncomingMessage): Locale | undefined => {\n const locale = getLocaleFromStorage({\n getCookie: (name: string) => getCookie(name, req.headers.cookie),\n });\n return locale;\n };\n\n /**\n * Appends locale to search params when routing mode is 'search-params'.\n */\n const appendLocaleSearchIfNeeded = (\n search: string | undefined,\n locale: Locale\n ): string | undefined => {\n if (mode !== 'search-params') return search;\n\n const params = new URLSearchParams(search ?? '');\n\n params.set('locale', locale);\n\n return `?${params.toString()}`;\n };\n\n /**\n * Extracts the locale from the URL pathname if present as the first segment.\n */\n const getPathLocale = (pathname: string): Locale | undefined => {\n // e.g. if pathname is /en/some/page or /en\n // we check if \"en\" is in your supportedLocales\n const segments = pathname.split('/').filter(Boolean);\n const firstSegment = segments[0];\n if (firstSegment && supportedLocales.includes(firstSegment as Locale)) {\n return firstSegment as Locale;\n }\n return undefined;\n };\n\n /**\n * Writes a 301 redirect response with the given new URL.\n */\n const redirectUrl = (\n res: ServerResponse<IncomingMessage>,\n newUrl: string,\n reason?: string,\n originalUrl?: string\n ) => {\n // Track redirect count to detect loops\n if (originalUrl) {\n const count = (redirectCounts.get(originalUrl) || 0) + 1;\n redirectCounts.set(originalUrl, count);\n\n if (count > MAX_REDIRECTS) {\n console.error('[REDIRECT LOOP DETECTED!]', {\n originalUrl,\n redirectCount: count,\n lastRedirectTo: newUrl,\n reason,\n });\n res.writeHead(500, { 'Content-Type': 'text/plain' });\n return res.end(\n `Redirect loop detected: ${count} redirects from ${originalUrl}`\n );\n }\n }\n\n res.writeHead(301, { Location: newUrl });\n return res.end();\n };\n\n /**\n * \"Rewrite\" the request internally by adjusting req.url;\n * we also set the locale in the response header if needed.\n */\n const rewriteUrl = (\n req: Connect.IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n newUrl: string,\n locale?: Locale\n ) => {\n if (req.url !== newUrl) {\n req.url = newUrl;\n }\n // If you want to mimic Next.js's behavior of setting a header for the locale:\n if (locale) {\n setLocaleInStorage(locale, {\n setHeader: (name: string, value: string) => {\n res.setHeader(name, value);\n req.headers[name] = value;\n },\n });\n }\n };\n\n /**\n * Constructs a new path string, optionally including a locale prefix, basePath, and search parameters.\n * - basePath: (e.g., '/myapp')\n * - locale: (e.g., 'en')\n * - currentPath:(e.g., '/products/shoes')\n * - search: (e.g., '?foo=bar')\n */\n const constructPath = (\n locale: Locale,\n currentPath: string,\n search?: string\n ) => {\n // Strip any incoming locale prefix if present\n const pathWithoutPrefix = currentPath.startsWith(`/${locale}`)\n ? currentPath.slice(`/${locale}`.length)\n : currentPath;\n\n // Ensure basePath always starts with '/', and remove trailing slash if needed\n const cleanBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;\n const normalizedBasePath = cleanBasePath.endsWith('/')\n ? cleanBasePath.slice(0, -1)\n : cleanBasePath;\n\n // In 'search-params' and 'no-prefix' modes, do not prefix the path with the locale\n if (mode === 'no-prefix' || mode === 'search-params') {\n const newPath = search\n ? `${pathWithoutPrefix || '/'}${search}`\n : pathWithoutPrefix || '/';\n return newPath;\n }\n\n // Check if path already starts with locale to avoid double-prefixing\n const pathWithLocalePrefix = currentPath.startsWith(`/${locale}`)\n ? currentPath\n : `/${locale}${currentPath === '/' ? '' : currentPath}`;\n\n let newPath = `${normalizedBasePath}${pathWithLocalePrefix}`;\n\n // Special case: if prefixDefault is false and locale is defaultLocale, remove the locale prefix\n if (!prefixDefault && locale === defaultLocale) {\n newPath = `${normalizedBasePath}${pathWithoutPrefix || '/'}`;\n }\n\n // Append search parameters if provided\n if (search) {\n newPath += search;\n }\n\n return newPath;\n };\n\n /* --------------------------------------------------------------------\n * Handlers that mirror Next.js style logic\n * --------------------------------------------------------------------\n */\n\n /**\n * If `noPrefix` is true, we never prefix the locale in the URL.\n * We simply rewrite the request to the same path, but with the best-chosen locale\n * in a header or search params if desired.\n */\n const handleNoPrefix = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n const pathLocale = getPathLocale(originalPath);\n // Determine the best locale\n let locale = storageLocale ?? defaultLocale;\n\n // Use fallback to localeDetector if no storage locale\n if (!storageLocale) {\n const detectedLocale = localeDetector(\n req.headers as Record<string, string>,\n supportedLocales,\n defaultLocale\n );\n locale = detectedLocale as Locale;\n }\n\n if (pathLocale) {\n const pathWithoutLocale =\n originalPath.slice(`/${pathLocale}`.length) || '/';\n\n const canonicalPath = getCanonicalPath(\n pathWithoutLocale,\n pathLocale,\n rewriteRules\n );\n\n const search = appendLocaleSearchIfNeeded(searchParams, pathLocale);\n\n const redirectPath = search\n ? `${canonicalPath}${search}`\n : `${canonicalPath}${searchParams ?? ''}`;\n\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);\n\n // In search-params mode, we need to redirect to add the locale search param\n if (mode === 'search-params') {\n // Check if locale search param already exists and matches the detected locale\n const existingSearchParams = new URLSearchParams(searchParams ?? '');\n const existingLocale = existingSearchParams.get('locale');\n\n // If the existing locale matches the detected locale, no redirect needed\n if (existingLocale === locale) {\n // For internal routing, we need to add the locale prefix so the framework can match [locale] param\n const internalPath = `/${locale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const rewritePath = `${internalPath}${searchParams ?? ''}`;\n\n // Rewrite internally (URL stays the same in browser, but internally routes to /[locale]/path)\n rewriteUrl(req, res, rewritePath, locale);\n return next();\n }\n\n // Locale param missing or doesn't match - redirect to add/update it\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const redirectPath = search\n ? `${originalPath}${search}`\n : `${originalPath}${searchParams ?? ''}`;\n\n // Redirect to add/update the locale search param (URL changes in browser)\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n // For no-prefix mode (not search-params), add locale prefix internally for routing\n const internalPath = `/${locale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n\n // Add search params if needed\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const rewritePath = search\n ? `${internalPath}${search}`\n : `${internalPath}${searchParams ?? ''}`;\n\n // Rewrite internally (URL stays the same in browser, but internally routes to /[locale]/path)\n rewriteUrl(req, res, rewritePath, locale);\n\n return next();\n };\n\n /**\n * The main prefix logic:\n * - If there's no pathLocale in the URL, we might want to detect & redirect or rewrite\n * - If there is a pathLocale, handle storage mismatch or default locale special cases\n */\n const handlePrefix = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n pathLocale?: Locale;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n // 1. If pathLocale is missing, handle\n if (!pathLocale) {\n handleMissingPathLocale({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n });\n return;\n }\n\n // 2. If pathLocale exists, handle it\n handleExistingPathLocale({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n originalUrl,\n });\n };\n\n /**\n * Handles requests where the locale is missing from the URL pathname.\n * We detect a locale from storage / headers / default, then either redirect or rewrite.\n */\n const handleMissingPathLocale = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n storageLocale?: Locale;\n originalUrl?: string;\n }) => {\n // 1. Choose the best locale\n let locale = (storageLocale ??\n localeDetector(\n req.headers as Record<string, string>,\n supportedLocales,\n defaultLocale\n )) as Locale;\n\n // If still invalid, fallback\n if (!supportedLocales.includes(locale)) {\n locale = defaultLocale;\n }\n\n // Resolve to canonical path.\n // If user visits /a-propos (implied 'fr'), we resolve to /about\n const canonicalPath = getCanonicalPath(originalPath, locale, rewriteRules);\n\n // Determine target localized path for redirection\n // /about + 'fr' -> /a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n locale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n // Construct new path - preserving original search params\n const search = appendLocaleSearchIfNeeded(searchParams, locale);\n const newPath = constructPath(locale, targetLocalizedPath, search);\n\n // If we always prefix default or if this is not the default locale, do a 301 redirect\n // so that the user sees the locale in the URL.\n if (prefixDefault || locale !== defaultLocale) {\n return redirectUrl(res, newPath, undefined, originalUrl);\n }\n\n // If we do NOT prefix the default locale, just rewrite in place using canonical path for framework matching.\n // searchParams MUST be preserved here — dropping them causes the framework (e.g. TanStack Start) to\n // see a URL with no search params, trigger a validateSearch normalisation redirect to the prefixed URL\n // (e.g. /en?page=1&...), which the middleware then strips back to /?..., creating an infinite loop.\n rewriteUrl(\n req,\n res,\n `/${locale}${canonicalPath === '/' ? '' : canonicalPath}${searchParams}`,\n locale\n );\n return next();\n };\n\n /**\n * Handles requests where the locale prefix is present in the pathname.\n */\n const handleExistingPathLocale = ({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n originalPath: string;\n searchParams: string;\n pathLocale: Locale;\n originalUrl?: string;\n }) => {\n const rawPath = originalPath.slice(`/${pathLocale}`.length);\n\n // Identify the Canonical Path (Internal path)\n // Ex: /a-propos (from URL) -> /about (Canonical)\n const canonicalPath = getCanonicalPath(rawPath, pathLocale, rewriteRules);\n\n // Redirect Pretty URL to Canonical URL (Vite Specific Requirement)\n // If request is /fr/a-props (Pretty) -> Redirect to /fr/about (Canonical)\n // This allows the SPA router to handle the route naturally.\n if (canonicalPath !== rawPath) {\n const internalUrl = `/${pathLocale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const redirectPath = searchParams\n ? `${internalUrl}${searchParams}`\n : internalUrl;\n\n return redirectUrl(res, redirectPath, undefined, originalUrl);\n }\n\n // In prefix modes, respect the URL path locale\n // The path locale takes precedence, and we'll update storage to match\n handleDefaultLocaleRedirect({\n req,\n res,\n next,\n searchParams,\n pathLocale,\n canonicalPath,\n originalUrl,\n });\n };\n\n /**\n * If the path locale is the default locale but we don't want to prefix the default, remove it.\n */\n const handleDefaultLocaleRedirect = ({\n req,\n res,\n next,\n searchParams,\n pathLocale,\n canonicalPath,\n originalUrl,\n }: {\n req: Connect.IncomingMessage;\n res: ServerResponse<IncomingMessage>;\n next: Connect.NextFunction;\n searchParams: string;\n pathLocale: Locale;\n canonicalPath: string;\n originalUrl?: string;\n }) => {\n // If we don't prefix default AND the path locale is the default locale -> remove it\n if (!prefixDefault && pathLocale === defaultLocale) {\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n // Construct path without prefix\n const cleanBasePath = basePath.startsWith('/')\n ? basePath\n : `/${basePath}`;\n const normalizedBasePath = cleanBasePath.endsWith('/')\n ? cleanBasePath.slice(0, -1)\n : cleanBasePath;\n\n let finalPath = targetLocalizedPath;\n if (finalPath.startsWith('/')) finalPath = finalPath.slice(1);\n\n const fullPath = `${normalizedBasePath}/${finalPath}`.replace(\n /\\/+/g,\n '/'\n );\n\n return redirectUrl(\n res,\n fullPath + (searchParams ?? ''),\n undefined,\n originalUrl\n );\n }\n\n // If we do prefix default or pathLocale != default, keep as is, but rewrite to canonical internally\n const internalUrl = `/${pathLocale}${\n canonicalPath === '/' ? '' : canonicalPath\n }`;\n const newPath = searchParams\n ? `${internalUrl}${searchParams}`\n : internalUrl;\n\n rewriteUrl(req, res, newPath, pathLocale);\n return next();\n };\n\n return {\n name: 'vite-intlayer-middleware-plugin',\n configureServer: (server) => {\n server.middlewares.use((req, res, next) => {\n // Bypass assets and special Vite endpoints\n if (\n // Custom ignore function\n (options?.ignore?.(req) ?? false) ||\n req.url?.startsWith('/node_modules') ||\n /**\n * /^@vite/ # HMR client and helpers\n * /^@fs/ # file-system import serving\n * /^@id/ # virtual module ids\n * /^@tanstack/start-router-manifest # Tanstack Start Router manifest\n */\n req.url?.startsWith('/@') ||\n /**\n * /^__vite_ping$ # health ping\n * /^__open-in-editor$\n * /^__manifest$ # Remix/RR7 lazyRouteDiscovery\n */\n req.url?.startsWith('/_') ||\n /**\n * ./myFile.js\n */\n req.url?.split('?')[0].match(/\\.[a-z]+$/i) // checks for file extensions\n ) {\n return next();\n }\n\n // Parse original URL for path and query\n const parsedUrl = parse(req.url ?? '/', true);\n const originalPath = parsedUrl.pathname ?? '/';\n const searchParams = parsedUrl.search ?? '';\n\n // Check if there's a locale prefix in the path FIRST\n const pathLocale = getPathLocale(originalPath);\n\n // Attempt to read the locale from storage (cookies, localStorage, etc.)\n const storageLocale = getStorageLocale(req);\n\n // CRITICAL FIX: If there's a valid pathLocale, it takes precedence over storage\n // This prevents race conditions when cookies are stale during locale switches\n const effectiveStorageLocale =\n pathLocale && supportedLocales.includes(pathLocale)\n ? pathLocale\n : storageLocale;\n\n // Store original URL for redirect tracking\n const originalUrl = req.url;\n\n // If noPrefix is true, we skip prefix logic altogether\n if (noPrefix) {\n handleNoPrefix({\n req,\n res,\n next,\n originalPath,\n searchParams,\n storageLocale: effectiveStorageLocale,\n originalUrl,\n });\n return;\n }\n\n // Otherwise, handle prefix logic\n handlePrefix({\n req,\n res,\n next,\n originalPath,\n searchParams,\n pathLocale,\n storageLocale: effectiveStorageLocale,\n originalUrl,\n });\n });\n\n // Clean up redirect counts periodically (every 100 requests)\n if (redirectCounts.size > 100) {\n redirectCounts.clear();\n }\n },\n };\n};\n\n/**\n * @deprecated Rename to intlayerProxy instead\n *\n * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayerMiddleware() ],\n * });\n * ```\n */\nexport const intlayerMiddleware = intlayerProxy;\n\n/**\n * @deprecated Rename to intlayerProxy instead\n * \n * A Vite plugin that integrates a logic similar to the Next.js intlayer middleware.\n\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayerMiddleware() ],\n * });\n * ```\n */\nexport const intLayerMiddlewarePlugin = intlayerProxy;\n"],"mappings":"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"}
@@ -1,2 +1,35 @@
1
- const e=(e,t)=>{let{optimize:n}=e.build,r=e.build.importMode??e.dictionary?.importMode;return{name:`vite-intlayer-simple-transform`,enforce:`pre`,apply:(e,t)=>{let i=t.command===`build`;return(n===void 0&&i||n===!0)&&(r===`dynamic`||r===`fetch`)},transform(e,n){if(!n.endsWith(`.vue`))return null;let r=n.split(`?`,1)[0];return!t.includes(r)||!e.includes(`useIntlayer`)?null:{code:e.replace(/(\s+|=\s*)useIntlayer\s*\(/g,`$1await useIntlayer(`),map:null}}}};export{e as intlayerVueAsyncPlugin};
1
+ //#region src/intlayerVueAsyncPlugin.ts
2
+ const intlayerVueAsyncPlugin = (configuration, filesList) => {
3
+ const { optimize } = configuration.build;
4
+ const importMode = configuration.build.importMode ?? configuration.dictionary?.importMode;
5
+ return {
6
+ name: "vite-intlayer-simple-transform",
7
+ enforce: "pre",
8
+ apply: (_config, env) => {
9
+ const isBuild = env.command === "build";
10
+ return (optimize === void 0 && isBuild || optimize === true) && (importMode === "dynamic" || importMode === "fetch");
11
+ },
12
+ transform(code, id) {
13
+ if (!id.endsWith(".vue")) return null;
14
+ /**
15
+ * Transform file as
16
+ * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts
17
+ * Into
18
+ * .../HelloWorld.vue
19
+ *
20
+ * Prevention for virtual file
21
+ */
22
+ const filename = id.split("?", 1)[0];
23
+ if (!filesList.includes(filename)) return null;
24
+ if (!code.includes("useIntlayer")) return null;
25
+ return {
26
+ code: code.replace(/(\s+|=\s*)useIntlayer\s*\(/g, "$1await useIntlayer("),
27
+ map: null
28
+ };
29
+ }
30
+ };
31
+ };
32
+
33
+ //#endregion
34
+ export { intlayerVueAsyncPlugin };
2
35
  //# sourceMappingURL=intlayerVueAsyncPlugin.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerVueAsyncPlugin.mjs","names":[],"sources":["../../src/intlayerVueAsyncPlugin.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\nexport const intlayerVueAsyncPlugin = (\n configuration: IntlayerConfig,\n filesList: string[]\n): PluginOption => {\n const { optimize } = configuration.build;\n const importMode =\n configuration.build.importMode ?? configuration.dictionary?.importMode;\n\n return {\n /**\n * On vue, we pre-insert the 'await' to the useIntlayer call\n * It will trigger the transformation of the async call by the vue compiler\n *\n * Then the second plugin will make the second transformation to replace the useIntlayer call by the useDictionaryDynamic call\n */\n name: 'vite-intlayer-simple-transform',\n enforce: 'pre', // Run before Vue so Vue sees the 'await'\n apply: (_config, env) => {\n // Only apply babel plugin if optimize is enabled\n\n const isBuild = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuild) || optimize === true;\n const isAsync = importMode === 'dynamic' || importMode === 'fetch';\n\n return isEnabled && isAsync;\n },\n\n transform(code, id) {\n // Only process .vue files\n // The await injection is only needed for Vue to trigger async component compilation\n if (!id.endsWith('.vue')) return null;\n\n /**\n * Transform file as\n * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts\n * Into\n * .../HelloWorld.vue\n *\n * Prevention for virtual file\n */\n const filename = id.split('?', 1)[0];\n\n if (!filesList.includes(filename)) return null;\n\n // Check if the file actually uses the composable to avoid unnecessary work\n if (!code.includes('useIntlayer')) return null;\n\n // B. Add 'await' to the function call\n // Matches: useIntlayer(args) -> await useIntlayer(args)\n // Note: Since we aliased the import above, 'useIntlayer' now refers to 'useDictionaryAsync'\n const transformedCode = code.replace(\n /(\\s+|=\\s*)useIntlayer\\s*\\(/g,\n '$1await useIntlayer('\n );\n\n return {\n code: transformedCode,\n map: null, // Simple string replace doesn't strictly need a sourcemap for this case\n };\n },\n };\n};\n"],"mappings":"AAGA,MAAa,GACX,EACA,IACiB,CACjB,GAAM,CAAE,YAAa,EAAc,MAC7B,EACJ,EAAc,MAAM,YAAc,EAAc,YAAY,WAE9D,MAAO,CAOL,KAAM,iCACN,QAAS,MACT,OAAQ,EAAS,IAAQ,CAGvB,IAAM,EAAU,EAAI,UAAY,QAKhC,OAHG,IAAa,IAAA,IAAa,GAAY,IAAa,MACtC,IAAe,WAAa,IAAe,UAK7D,UAAU,EAAM,EAAI,CAGlB,GAAI,CAAC,EAAG,SAAS,OAAO,CAAE,OAAO,KAUjC,IAAM,EAAW,EAAG,MAAM,IAAK,EAAE,CAAC,GAelC,MAbI,CAAC,EAAU,SAAS,EAAS,EAG7B,CAAC,EAAK,SAAS,cAAc,CAAS,KAUnC,CACL,KANsB,EAAK,QAC3B,8BACA,uBACD,CAIC,IAAK,KACN,EAEJ"}
1
+ {"version":3,"file":"intlayerVueAsyncPlugin.mjs","names":[],"sources":["../../src/intlayerVueAsyncPlugin.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\nexport const intlayerVueAsyncPlugin = (\n configuration: IntlayerConfig,\n filesList: string[]\n): PluginOption => {\n const { optimize } = configuration.build;\n const importMode =\n configuration.build.importMode ?? configuration.dictionary?.importMode;\n\n return {\n /**\n * On vue, we pre-insert the 'await' to the useIntlayer call\n * It will trigger the transformation of the async call by the vue compiler\n *\n * Then the second plugin will make the second transformation to replace the useIntlayer call by the useDictionaryDynamic call\n */\n name: 'vite-intlayer-simple-transform',\n enforce: 'pre', // Run before Vue so Vue sees the 'await'\n apply: (_config, env) => {\n // Only apply babel plugin if optimize is enabled\n\n const isBuild = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuild) || optimize === true;\n const isAsync = importMode === 'dynamic' || importMode === 'fetch';\n\n return isEnabled && isAsync;\n },\n\n transform(code, id) {\n // Only process .vue files\n // The await injection is only needed for Vue to trigger async component compilation\n if (!id.endsWith('.vue')) return null;\n\n /**\n * Transform file as\n * .../HelloWorld.vue?vue&type=script&setup=true&lang.ts\n * Into\n * .../HelloWorld.vue\n *\n * Prevention for virtual file\n */\n const filename = id.split('?', 1)[0];\n\n if (!filesList.includes(filename)) return null;\n\n // Check if the file actually uses the composable to avoid unnecessary work\n if (!code.includes('useIntlayer')) return null;\n\n // B. Add 'await' to the function call\n // Matches: useIntlayer(args) -> await useIntlayer(args)\n // Note: Since we aliased the import above, 'useIntlayer' now refers to 'useDictionaryAsync'\n const transformedCode = code.replace(\n /(\\s+|=\\s*)useIntlayer\\s*\\(/g,\n '$1await useIntlayer('\n );\n\n return {\n code: transformedCode,\n map: null, // Simple string replace doesn't strictly need a sourcemap for this case\n };\n },\n };\n};\n"],"mappings":";AAGA,MAAa,0BACX,eACA,cACiB;CACjB,MAAM,EAAE,aAAa,cAAc;CACnC,MAAM,aACJ,cAAc,MAAM,cAAc,cAAc,YAAY;AAE9D,QAAO;EAOL,MAAM;EACN,SAAS;EACT,QAAQ,SAAS,QAAQ;GAGvB,MAAM,UAAU,IAAI,YAAY;AAKhC,WAHG,aAAa,UAAa,WAAY,aAAa,UACtC,eAAe,aAAa,eAAe;;EAK7D,UAAU,MAAM,IAAI;AAGlB,OAAI,CAAC,GAAG,SAAS,OAAO,CAAE,QAAO;;;;;;;;;GAUjC,MAAM,WAAW,GAAG,MAAM,KAAK,EAAE,CAAC;AAElC,OAAI,CAAC,UAAU,SAAS,SAAS,CAAE,QAAO;AAG1C,OAAI,CAAC,KAAK,SAAS,cAAc,CAAE,QAAO;AAU1C,UAAO;IACL,MANsB,KAAK,QAC3B,+BACA,uBACD;IAIC,KAAK;IACN;;EAEJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-intlayer",
3
- "version": "8.4.4",
3
+ "version": "8.4.5",
4
4
  "private": false,
5
5
  "description": "A Vite plugin for seamless internationalization (i18n), providing locale detection, redirection, and environment-based configuration",
6
6
  "keywords": [
@@ -77,13 +77,13 @@
77
77
  },
78
78
  "dependencies": {
79
79
  "@babel/core": "7.29.0",
80
- "@intlayer/babel": "8.4.4",
81
- "@intlayer/chokidar": "8.4.4",
82
- "@intlayer/config": "8.4.4",
83
- "@intlayer/core": "8.4.4",
84
- "@intlayer/dictionaries-entry": "8.4.4",
85
- "@intlayer/types": "8.4.4",
86
- "intlayer": "8.4.4"
80
+ "@intlayer/babel": "8.4.5",
81
+ "@intlayer/chokidar": "8.4.5",
82
+ "@intlayer/config": "8.4.5",
83
+ "@intlayer/core": "8.4.5",
84
+ "@intlayer/dictionaries-entry": "8.4.5",
85
+ "@intlayer/types": "8.4.5",
86
+ "intlayer": "8.4.5"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@types/babel__core": "7.20.5",