vite-intlayer 9.3.1 → 9.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  import { intlayerCompiler } from "./IntlayerCompilerPlugin.mjs";
2
2
  import { intlayerMinify } from "./intlayerMinifyPlugin.mjs";
3
3
  import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
4
+ import { createDictionaryModuleParser, intlayerChunk, resolveBoundaries, toBoundaryName } from "./intlayerChunkPlugin.mjs";
4
5
  import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
6
+ import { intlayerPreload } from "./intlayerPreloadPlugin.mjs";
5
7
  import { createIntlayerProxyHandler, intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.mjs";
6
8
  import { intLayerPlugin, intlayer, intlayerPlugin } from "./intlayerPlugin.mjs";
7
9
 
8
- export { createIntlayerProxyHandler, intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerProxy, intlayerPrune };
10
+ export { createDictionaryModuleParser, createIntlayerProxyHandler, intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerChunk, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerPreload, intlayerProxy, intlayerPrune, resolveBoundaries, toBoundaryName };
@@ -0,0 +1,181 @@
1
+ import { colorizeNumber, getAppLogger } from "@intlayer/config/logger";
2
+ import { normalizePath } from "@intlayer/config/utils";
3
+ import { DYNAMIC_DICTIONARIES_JSON_SUBDIR } from "@intlayer/config/defaultValues";
4
+
5
+ //#region src/intlayerChunkPlugin.ts
6
+ /**
7
+ * Builds the parser that recognises a per-locale dictionary chunk emitted in
8
+ * `dynamic` import mode.
9
+ *
10
+ * The layout is read relative to `dynamicDictionariesDir` rather than matched
11
+ * anywhere in the path, so an application file that happens to sit under its own
12
+ * `json/<something>/<something>.json` cannot be mistaken for a dictionary.
13
+ * Everything between the key and the locale is a qualifier dimension
14
+ * (collections, variants), which is why the middle segments are skipped instead
15
+ * of being required to be absent.
16
+ *
17
+ * @param dynamicDictionariesDir - Absolute, POSIX-normalized dictionaries root.
18
+ * @returns Parser returning the key and locale, or `null` for any other module.
19
+ */
20
+ const createDictionaryModuleParser = (dynamicDictionariesDir) => {
21
+ const prefix = `${dynamicDictionariesDir}/${DYNAMIC_DICTIONARIES_JSON_SUBDIR}/`;
22
+ return (moduleId) => {
23
+ const posixId = normalizePath(moduleId.split("?", 1)[0] ?? moduleId);
24
+ if (!posixId.startsWith(prefix)) return null;
25
+ const segments = posixId.slice(prefix.length).split("/");
26
+ if (segments.length < 2) return null;
27
+ const fileName = segments[segments.length - 1];
28
+ if (!fileName.endsWith(".json")) return null;
29
+ return {
30
+ key: segments[0],
31
+ locale: fileName.slice(0, -5)
32
+ };
33
+ };
34
+ };
35
+ /** Characters that are unsafe in a chunk file name. */
36
+ const UNSAFE_NAME_CHARACTERS = /[^a-zA-Z0-9]+/g;
37
+ /**
38
+ * Short, stable digest of a module id, appended to a boundary name so two
39
+ * boundaries sharing a basename (`index.tsx` and `index.tsx`) do not collapse
40
+ * into a single chunk.
41
+ */
42
+ const digest = (value) => {
43
+ let hash = 5381;
44
+ for (let index = 0; index < value.length; index++) hash = (hash << 5) + hash + value.charCodeAt(index) >>> 0;
45
+ return hash.toString(36).slice(0, 6);
46
+ };
47
+ /** Turns a boundary module id into a readable chunk-name fragment. */
48
+ const toBoundaryName = (moduleId) => {
49
+ return `${(normalizePath(moduleId).split("/").pop() ?? "chunk").replace(/\.[^.]+$/, "").replace(UNSAFE_NAME_CHARACTERS, "-").replace(/^-+|-+$/g, "") || "chunk"}-${digest(moduleId)}`;
50
+ };
51
+ /**
52
+ * Walks up the import graph from a dictionary JSON to the code-split boundaries
53
+ * that reach it.
54
+ *
55
+ * A module pulled in through `import()` — a `React.lazy` section, a split
56
+ * route — begins its own chunk, so it is the unit whose dictionaries should
57
+ * travel together: one request per Suspense boundary rather than one per
58
+ * dictionary. The walk deliberately starts from the JSON's importers rather
59
+ * than the JSON itself, because *every* dictionary is reached through
60
+ * `import()` and would otherwise be read as its own boundary.
61
+ *
62
+ * @returns Boundary ids reaching this dictionary, empty when the graph is
63
+ * unavailable (the module was pruned, or resolution failed).
64
+ */
65
+ const resolveBoundaries = (jsonModuleId, context) => {
66
+ const jsonNode = context.getModuleInfo(jsonModuleId);
67
+ if (!jsonNode) return [];
68
+ const visited = /* @__PURE__ */ new Set();
69
+ const boundaries = /* @__PURE__ */ new Set();
70
+ const queue = [...jsonNode.dynamicImporters, ...jsonNode.importers];
71
+ while (queue.length > 0) {
72
+ const currentId = queue.pop();
73
+ if (visited.has(currentId)) continue;
74
+ visited.add(currentId);
75
+ const node = context.getModuleInfo(currentId);
76
+ if (!node) continue;
77
+ const isCodeSplitBoundary = node.dynamicImporters.length > 0;
78
+ const isGraphRoot = node.isEntry || node.importers.length === 0;
79
+ if (isCodeSplitBoundary || isGraphRoot) {
80
+ boundaries.add(currentId);
81
+ continue;
82
+ }
83
+ queue.push(...node.importers);
84
+ }
85
+ return [...boundaries].sort();
86
+ };
87
+ /**
88
+ * Groups dynamically imported dictionaries by the code-split boundary that
89
+ * consumes them, so each lazily loaded page or section fetches its content in
90
+ * a single request instead of one per dictionary.
91
+ *
92
+ * Without this, `importMode: 'dynamic'` emits a chunk per dictionary per
93
+ * locale. A page assembled from many components then issues dozens of ~3 KB
94
+ * requests, and because each is discovered only once its consumer has
95
+ * executed, they land in the last wave of the request waterfall — page text
96
+ * arrives after everything else.
97
+ *
98
+ * Switching to `importMode: 'static'` would collapse those requests but inline
99
+ * every locale into the bundle, which does not scale past a handful of
100
+ * languages. Grouping keeps the per-locale split and removes the fragmentation.
101
+ *
102
+ * Dictionaries reached from more than one boundary are placed in a shared
103
+ * chunk, so no page ships another page's content and the bytes are not
104
+ * duplicated across chunks.
105
+ *
106
+ * Because a boundary's dictionaries share one chunk, warming that chunk warms
107
+ * the whole boundary in a single request — which is why the top-level-await
108
+ * preload (`intlayerPreload`) costs one request per boundary rather than one
109
+ * per dictionary, without any page loading content belonging to another.
110
+ *
111
+ * @param intlayerConfig - Resolved Intlayer configuration.
112
+ */
113
+ const intlayerChunk = (intlayerConfig) => {
114
+ const dynamicDictionariesDir = normalizePath(intlayerConfig.system.dynamicDictionariesDir);
115
+ const parseDictionaryModule = createDictionaryModuleParser(dynamicDictionariesDir);
116
+ const appLogger = getAppLogger(intlayerConfig);
117
+ /** Dictionary modules assigned to each group, for the build summary. */
118
+ const grouped = /* @__PURE__ */ new Map();
119
+ /** Dictionaries the graph walk could not attribute to any boundary. */
120
+ const unresolved = [];
121
+ /** Records a dictionary against its group and returns the group name. */
122
+ const assign = (groupName, dictionaryKey) => {
123
+ const members = grouped.get(groupName) ?? [];
124
+ members.push(dictionaryKey);
125
+ grouped.set(groupName, members);
126
+ return groupName;
127
+ };
128
+ const isDictionaryModule = (moduleId) => parseDictionaryModule(moduleId) !== null;
129
+ return {
130
+ name: "vite-intlayer-chunk-plugin",
131
+ /**
132
+ * Grouping only exists to undo build-time fragmentation, so the dev server —
133
+ * which serves modules unbundled — is skipped.
134
+ *
135
+ * The build is deliberately *not* gated on the configured `importMode`: a
136
+ * `.content` file can set `importMode: 'dynamic'` on a single dictionary
137
+ * under a `static` or `fetch` global mode, and those dictionaries fragment
138
+ * exactly like a fully dynamic build. Modules that reach the graph are the
139
+ * honest signal, so the group's `test` does the gating instead — it simply
140
+ * matches nothing when no dictionary resolves to dynamic.
141
+ */
142
+ apply: (_viteConfig, env) => intlayerConfig.build.chunkGrouping && env.command === "build",
143
+ config: () => {
144
+ return { environments: { client: { build: { rolldownOptions: { output: { codeSplitting: { groups: [{
145
+ name: (moduleId, context) => {
146
+ const dictionary = parseDictionaryModule(moduleId);
147
+ if (!dictionary) return null;
148
+ const boundaries = resolveBoundaries(moduleId, context);
149
+ if (boundaries.length === 0) {
150
+ unresolved.push(moduleId);
151
+ return null;
152
+ }
153
+ const groupName = `intlayer-${boundaries.length > 1 ? "shared" : toBoundaryName(boundaries[0])}-${dictionary.locale}`;
154
+ return assign(groupName, dictionary.key);
155
+ },
156
+ test: isDictionaryModule,
157
+ minSize: 0,
158
+ minShareCount: 1,
159
+ priority: 50
160
+ }] } } } } } } };
161
+ },
162
+ /**
163
+ * Reports what the grouping actually did. Silence here means the group
164
+ * never matched — the usual cause is an `importMode` that emits no
165
+ * per-dictionary chunks at all.
166
+ */
167
+ closeBundle() {
168
+ const groupCount = grouped.size;
169
+ if (groupCount === 0 && unresolved.length === 0) return;
170
+ const dictionaryCount = [...grouped.values()].reduce((total, members) => total + members.length, 0);
171
+ appLogger(`Grouped ${colorizeNumber(dictionaryCount)} dictionary chunks into ${colorizeNumber(groupCount)} per-boundary chunks.`);
172
+ if (unresolved.length > 0) appLogger(`${unresolved.length} dictionaries had no resolvable boundary and kept their own chunk.`, { level: "warn" });
173
+ grouped.clear();
174
+ unresolved.length = 0;
175
+ }
176
+ };
177
+ };
178
+
179
+ //#endregion
180
+ export { createDictionaryModuleParser, intlayerChunk, resolveBoundaries, toBoundaryName };
181
+ //# sourceMappingURL=intlayerChunkPlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerChunkPlugin.mjs","names":[],"sources":["../../src/intlayerChunkPlugin.ts"],"sourcesContent":["import { DYNAMIC_DICTIONARIES_JSON_SUBDIR } from '@intlayer/config/defaultValues';\nimport { colorizeNumber, getAppLogger } from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Plugin } from 'vite';\n\n/**\n * Module-graph accessor Rolldown passes to a chunk-name function.\n *\n * Declared structurally rather than imported from `rolldown` so this plugin\n * keeps working against the Rolldown bundled inside Vite, whose types are not\n * re-exported.\n */\nexport type ChunkingContext = {\n getModuleInfo: (moduleId: string) => ModuleGraphNode | null;\n};\n\nexport type ModuleGraphNode = {\n id: string;\n isEntry: boolean;\n /** Ids of the modules that statically `import` this one. */\n importers: string[];\n /** Ids of the modules that reach this one through `import()`. */\n dynamicImporters: string[];\n};\n\n/** A per-locale dictionary chunk, identified from its module id. */\nexport type DictionaryModule = {\n key: string;\n locale: string;\n};\n\n/**\n * Builds the parser that recognises a per-locale dictionary chunk emitted in\n * `dynamic` import mode.\n *\n * The layout is read relative to `dynamicDictionariesDir` rather than matched\n * anywhere in the path, so an application file that happens to sit under its own\n * `json/<something>/<something>.json` cannot be mistaken for a dictionary.\n * Everything between the key and the locale is a qualifier dimension\n * (collections, variants), which is why the middle segments are skipped instead\n * of being required to be absent.\n *\n * @param dynamicDictionariesDir - Absolute, POSIX-normalized dictionaries root.\n * @returns Parser returning the key and locale, or `null` for any other module.\n */\nexport const createDictionaryModuleParser = (\n dynamicDictionariesDir: string\n): ((moduleId: string) => DictionaryModule | null) => {\n const prefix = `${dynamicDictionariesDir}/${DYNAMIC_DICTIONARIES_JSON_SUBDIR}/`;\n\n return (moduleId) => {\n // Vite appends query suffixes (`?import`, `?url`) to module ids.\n const posixId = normalizePath(moduleId.split('?', 1)[0] ?? moduleId);\n if (!posixId.startsWith(prefix)) return null;\n\n const segments = posixId.slice(prefix.length).split('/');\n // `<key>/…qualifier segments…/<locale>.json` — at least a key and a file.\n if (segments.length < 2) return null;\n\n const fileName = segments[segments.length - 1]!;\n if (!fileName.endsWith('.json')) return null;\n\n return {\n key: segments[0]!,\n locale: fileName.slice(0, -'.json'.length),\n };\n };\n};\n\n/** Characters that are unsafe in a chunk file name. */\nconst UNSAFE_NAME_CHARACTERS = /[^a-zA-Z0-9]+/g;\n\n/**\n * Short, stable digest of a module id, appended to a boundary name so two\n * boundaries sharing a basename (`index.tsx` and `index.tsx`) do not collapse\n * into a single chunk.\n */\nconst digest = (value: string): string => {\n let hash = 5381;\n for (let index = 0; index < value.length; index++) {\n hash = ((hash << 5) + hash + value.charCodeAt(index)) >>> 0;\n }\n return hash.toString(36).slice(0, 6);\n};\n\n/** Turns a boundary module id into a readable chunk-name fragment. */\nexport const toBoundaryName = (moduleId: string): string => {\n const fileName = normalizePath(moduleId).split('/').pop() ?? 'chunk';\n const base = fileName\n .replace(/\\.[^.]+$/, '')\n .replace(UNSAFE_NAME_CHARACTERS, '-')\n .replace(/^-+|-+$/g, '');\n return `${base || 'chunk'}-${digest(moduleId)}`;\n};\n\n/**\n * Walks up the import graph from a dictionary JSON to the code-split boundaries\n * that reach it.\n *\n * A module pulled in through `import()` — a `React.lazy` section, a split\n * route — begins its own chunk, so it is the unit whose dictionaries should\n * travel together: one request per Suspense boundary rather than one per\n * dictionary. The walk deliberately starts from the JSON's importers rather\n * than the JSON itself, because *every* dictionary is reached through\n * `import()` and would otherwise be read as its own boundary.\n *\n * @returns Boundary ids reaching this dictionary, empty when the graph is\n * unavailable (the module was pruned, or resolution failed).\n */\nexport const resolveBoundaries = (\n jsonModuleId: string,\n context: ChunkingContext\n): string[] => {\n const jsonNode = context.getModuleInfo(jsonModuleId);\n if (!jsonNode) return [];\n\n const visited = new Set<string>();\n const boundaries = new Set<string>();\n const queue: string[] = [...jsonNode.dynamicImporters, ...jsonNode.importers];\n\n while (queue.length > 0) {\n const currentId = queue.pop() as string;\n if (visited.has(currentId)) continue;\n visited.add(currentId);\n\n const node = context.getModuleInfo(currentId);\n if (!node) continue;\n\n const isCodeSplitBoundary = node.dynamicImporters.length > 0;\n const isGraphRoot = node.isEntry || node.importers.length === 0;\n\n if (isCodeSplitBoundary || isGraphRoot) {\n boundaries.add(currentId);\n continue;\n }\n\n queue.push(...node.importers);\n }\n\n return [...boundaries].sort();\n};\n\n/**\n * Groups dynamically imported dictionaries by the code-split boundary that\n * consumes them, so each lazily loaded page or section fetches its content in\n * a single request instead of one per dictionary.\n *\n * Without this, `importMode: 'dynamic'` emits a chunk per dictionary per\n * locale. A page assembled from many components then issues dozens of ~3 KB\n * requests, and because each is discovered only once its consumer has\n * executed, they land in the last wave of the request waterfall — page text\n * arrives after everything else.\n *\n * Switching to `importMode: 'static'` would collapse those requests but inline\n * every locale into the bundle, which does not scale past a handful of\n * languages. Grouping keeps the per-locale split and removes the fragmentation.\n *\n * Dictionaries reached from more than one boundary are placed in a shared\n * chunk, so no page ships another page's content and the bytes are not\n * duplicated across chunks.\n *\n * Because a boundary's dictionaries share one chunk, warming that chunk warms\n * the whole boundary in a single request — which is why the top-level-await\n * preload (`intlayerPreload`) costs one request per boundary rather than one\n * per dictionary, without any page loading content belonging to another.\n *\n * @param intlayerConfig - Resolved Intlayer configuration.\n */\nexport const intlayerChunk = (intlayerConfig: IntlayerConfig): Plugin => {\n const dynamicDictionariesDir = normalizePath(\n intlayerConfig.system.dynamicDictionariesDir\n );\n\n const parseDictionaryModule = createDictionaryModuleParser(\n dynamicDictionariesDir\n );\n\n const appLogger = getAppLogger(intlayerConfig);\n\n /** Dictionary modules assigned to each group, for the build summary. */\n const grouped = new Map<string, string[]>();\n /** Dictionaries the graph walk could not attribute to any boundary. */\n const unresolved: string[] = [];\n\n /** Records a dictionary against its group and returns the group name. */\n const assign = (groupName: string, dictionaryKey: string): string => {\n const members = grouped.get(groupName) ?? [];\n members.push(dictionaryKey);\n grouped.set(groupName, members);\n return groupName;\n };\n\n const isDictionaryModule = (moduleId: string): boolean =>\n parseDictionaryModule(moduleId) !== null;\n\n return {\n name: 'vite-intlayer-chunk-plugin',\n\n /**\n * Grouping only exists to undo build-time fragmentation, so the dev server —\n * which serves modules unbundled — is skipped.\n *\n * The build is deliberately *not* gated on the configured `importMode`: a\n * `.content` file can set `importMode: 'dynamic'` on a single dictionary\n * under a `static` or `fetch` global mode, and those dictionaries fragment\n * exactly like a fully dynamic build. Modules that reach the graph are the\n * honest signal, so the group's `test` does the gating instead — it simply\n * matches nothing when no dictionary resolves to dynamic.\n */\n apply: (_viteConfig, env) =>\n intlayerConfig.build.chunkGrouping && env.command === 'build',\n\n config: () => {\n return {\n // Scoped to the client build. Dictionary chunks only exist to be\n // fetched by a browser, and `build` at the top level would apply to\n // every environment — re-chunking the server bundle too, where\n // reordering a dependency's modules can break their initialisation.\n environments: {\n client: {\n build: {\n rolldownOptions: {\n output: {\n codeSplitting: {\n groups: [\n {\n name: (moduleId: string, context: ChunkingContext) => {\n const dictionary = parseDictionaryModule(moduleId);\n if (!dictionary) return null;\n\n const boundaries = resolveBoundaries(\n moduleId,\n context\n );\n if (boundaries.length === 0) {\n unresolved.push(moduleId);\n return null;\n }\n\n const scope =\n boundaries.length > 1\n ? 'shared'\n : toBoundaryName(boundaries[0]!);\n const groupName = `intlayer-${scope}-${dictionary.locale}`;\n\n return assign(groupName, dictionary.key);\n },\n test: isDictionaryModule,\n // Dictionaries are small individually; the point is to\n // group them, so the surrounding build's size floor must\n // not dissolve these chunks back into their parents.\n minSize: 0,\n minShareCount: 1,\n priority: 50,\n },\n ],\n },\n },\n },\n },\n },\n },\n };\n },\n\n /**\n * Reports what the grouping actually did. Silence here means the group\n * never matched — the usual cause is an `importMode` that emits no\n * per-dictionary chunks at all.\n */\n closeBundle() {\n const groupCount = grouped.size;\n if (groupCount === 0 && unresolved.length === 0) return;\n\n const dictionaryCount = [...grouped.values()].reduce(\n (total, members) => total + members.length,\n 0\n );\n\n appLogger(\n `Grouped ${colorizeNumber(dictionaryCount)} dictionary chunks into ${colorizeNumber(groupCount)} per-boundary chunks.`\n );\n\n if (unresolved.length > 0) {\n appLogger(\n `${unresolved.length} dictionaries had no resolvable boundary and kept their own chunk.`,\n { level: 'warn' }\n );\n }\n\n grouped.clear();\n unresolved.length = 0;\n },\n };\n};\n"],"mappings":"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"}
@@ -1,12 +1,22 @@
1
- import { createIntlayerProxyHandler } from "./intlayerProxyPlugin.mjs";
1
+ import { createProxyHandler } from "./intlayerProxyHandler.mjs";
2
2
  import { getAppLogger } from "@intlayer/config/logger";
3
- import { getConfiguration } from "@intlayer/config/node";
4
3
  import { formatProxyEnabledMessage } from "@intlayer/core/localization";
4
+ import * as builtConfiguration from "@intlayer/config/built";
5
5
 
6
6
  //#region src/intlayerNitroHandler.ts
7
- const intlayerConfig = getConfiguration();
7
+ /**
8
+ * The configuration as a plain JSON object.
9
+ *
10
+ * Bundlers alias `@intlayer/config/built` to the generated
11
+ * `.intlayer/config/configuration.mjs`, which inlines the resolved
12
+ * configuration at build time. Loading it through `@intlayer/config/node`
13
+ * instead would transpile `intlayer.config.ts` with `esbuild` on every server
14
+ * start, making the built server unable to boot without `node_modules` next to
15
+ * it - which is exactly what a Nitro deployment output is.
16
+ */
17
+ const intlayerConfig = builtConfiguration;
8
18
  getAppLogger(intlayerConfig)(formatProxyEnabledMessage(false), { level: "info" });
9
- const nodeMiddleware = createIntlayerProxyHandler();
19
+ const nodeMiddleware = createProxyHandler({ configuration: intlayerConfig });
10
20
  /**
11
21
  * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).
12
22
  *
@@ -15,7 +25,7 @@ const nodeMiddleware = createIntlayerProxyHandler();
15
25
  * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with
16
26
  * "undefined is not an object (evaluating 'event.node.req')".
17
27
  *
18
- * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware
28
+ * It bridges h3 v2 events to the Node.js-style proxy middleware
19
29
  * via lightweight IncomingMessage / ServerResponse shims:
20
30
  *
21
31
  * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends
@@ -31,7 +41,7 @@ var intlayerNitroHandler_default = async (event) => new Promise((resolve) => {
31
41
  /**
32
42
  * Minimal IncomingMessage shim.
33
43
  *
34
- * Only the fields actually read by createIntlayerProxyHandler are populated:
44
+ * Only the fields actually read by the proxy middleware are populated:
35
45
  * - url : the current pathname + search, modified for rewrites
36
46
  * - headers.cookie : locale cookie detection
37
47
  * - headers.host : domain-based locale routing
@@ -60,7 +70,7 @@ var intlayerNitroHandler_default = async (event) => new Promise((resolve) => {
60
70
  /**
61
71
  * Minimal ServerResponse shim.
62
72
  *
63
- * Implements only the methods that createIntlayerProxyHandler invokes:
73
+ * Implements only the methods that the proxy middleware invokes:
64
74
  * writeHead() — status + Location header for 301 redirects
65
75
  * setHeader() — Set-Cookie written by setLocaleInStorageServer
66
76
  * getHeader() — defensive read-back (not strictly required but safe)
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerNitroHandler.mjs","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { formatProxyEnabledMessage } from '@intlayer/core/localization';\nimport { createIntlayerProxyHandler } from './intlayerProxyPlugin';\n\n/**\n * Minimal duck-type for h3 v2's H3Event.\n *\n * We intentionally avoid importing from 'h3' to keep this file runtime-agnostic —\n * Nitro bundles h3 internally and provides the populated event at runtime. Using a\n * structural type here means the file compiles without h3 in devDependencies and\n * works with any h3 v2-compatible runtime (Bun, Deno, Node).\n */\ntype H3EventLike = {\n /**\n * pathname + search — a computed getter on H3Event:\n * `return this.url.pathname + this.url.search`\n */\n readonly path: string;\n /**\n * Full URL object — a **plain property** (not a getter) on H3Event, safe to\n * replace for internal URL rewrites. After assignment, `event.path` will\n * automatically reflect the new pathname + search via the getter.\n */\n url: URL;\n /**\n * Web Fetch API Headers — always populated in h3 v2 regardless of preset\n * (Node, Bun, Deno). Use `.get(name)` instead of bracket-access.\n */\n readonly headers: Headers;\n /**\n * Lazy response object — created on first access; its `headers` carry outgoing\n * response headers (e.g. Set-Cookie) that h3 merges into the HTTP response.\n */\n readonly res: {\n readonly headers: Headers;\n };\n};\n\nconst intlayerConfig = getConfiguration();\nconst logger = getAppLogger(intlayerConfig);\n// A Nitro server is a production server, so the stored locale always drives\n// redirects here — hence the `false`. This runs once per server process; the\n// Vite preview server deliberately does not add a second proxy layer on top of\n// this one (see `configurePreviewServer` in `intlayerProxyPlugin`).\nlogger(formatProxyEnabledMessage(false), {\n level: 'info',\n});\n\nconst nodeMiddleware = createIntlayerProxyHandler();\n\n/**\n * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).\n *\n * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event\n * model exclusively and is therefore compatible with ALL Nitro presets — including Bun\n * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with\n * \"undefined is not an object (evaluating 'event.node.req')\".\n *\n * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware\n * via lightweight IncomingMessage / ServerResponse shims:\n *\n * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends\n * the correct HTTP response to the browser.\n * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten\n * URL so `event.path` (a getter) returns the new pathname for downstream handlers and\n * the Nitro router.\n * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to\n * the next handler / route.\n */\nexport default async (event: H3EventLike): Promise<Response | void> =>\n new Promise<Response | void>((resolve) => {\n const initialPath = event.path;\n\n /**\n * Minimal IncomingMessage shim.\n *\n * Only the fields actually read by createIntlayerProxyHandler are populated:\n * - url : the current pathname + search, modified for rewrites\n * - headers.cookie : locale cookie detection\n * - headers.host : domain-based locale routing\n * - headers.accept-language : browser Accept-Language fallback\n * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups\n * - headers.x-nitro-prerender : marks a request issued by Nitro's\n * prerenderer, which keeps the generated page URL-driven\n *\n * headers must be a mutable plain object because setLocaleInStorageServer\n * writes Set-Cookie back via req.headers[name] = value.\n */\n const fakeReq = {\n url: initialPath,\n method: 'GET',\n headers: {\n cookie: event.headers.get('cookie') ?? '',\n host: event.headers.get('host') ?? '',\n 'accept-language': event.headers.get('accept-language') ?? '',\n 'x-forwarded-host': event.headers.get('x-forwarded-host') ?? '',\n 'x-forwarded-proto': event.headers.get('x-forwarded-proto') ?? '',\n 'x-nitro-prerender': event.headers.get('x-nitro-prerender') ?? '',\n } as Record<string, string>,\n } as unknown as IncomingMessage;\n\n let responseStatusCode = 200;\n const accumulatedHeaders: Record<string, string> = {};\n\n /**\n * Minimal ServerResponse shim.\n *\n * Implements only the methods that createIntlayerProxyHandler invokes:\n * writeHead() — status + Location header for 301 redirects\n * setHeader() — Set-Cookie written by setLocaleInStorageServer\n * getHeader() — defensive read-back (not strictly required but safe)\n * end() — finalises the response; for redirects this returns a\n * Web API Response object that Nitro sends to the client\n */\n const fakeRes = {\n writeHead(\n statusCode: number,\n headersArg?: Record<string, string | string[] | number> | string\n ) {\n // Capture the status code and any headers supplied alongside writeHead.\n responseStatusCode = statusCode;\n if (headersArg && typeof headersArg === 'object') {\n for (const [key, value] of Object.entries(headersArg)) {\n accumulatedHeaders[key.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n }\n }\n return fakeRes;\n },\n setHeader(name: string, value: string | number | string[]) {\n // Capture Set-Cookie and other outgoing headers.\n accumulatedHeaders[name.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n return fakeRes;\n },\n getHeader(name: string) {\n return accumulatedHeaders[name.toLowerCase()];\n },\n getHeaders() {\n return { ...accumulatedHeaders };\n },\n end(body?: string | Buffer | null) {\n // Build a Web API Response from accumulated status + headers + body.\n // For 3xx redirects the body is intentionally null.\n const webHeaders = new Headers();\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n webHeaders.set(key, value);\n }\n const isRedirect =\n responseStatusCode >= 300 && responseStatusCode < 400;\n resolve(\n new Response(\n isRedirect ? null : typeof body === 'string' ? body : null,\n {\n status: responseStatusCode,\n headers: webHeaders,\n }\n )\n );\n return fakeRes;\n },\n headersSent: false,\n } as unknown as ServerResponse<IncomingMessage>;\n\n nodeMiddleware(fakeReq, fakeRes, () => {\n // Middleware called next() — either a URL rewrite or a true pass-through.\n const rewrittenPath = fakeReq.url as string;\n\n if (rewrittenPath !== initialPath) {\n // The middleware rewrote the URL (e.g. /about → /en/about for locale prefix).\n // Replace event.url so that event.path (the getter: url.pathname + url.search)\n // returns the new path and the Nitro router matches the correct route.\n //\n // event.url is a plain property on h3 v2's H3Event (not a getter), so direct\n // assignment is safe. We use event.url.origin as the base so relative paths\n // resolve correctly; for path-only requests origin defaults to http://localhost.\n try {\n event.url = new URL(rewrittenPath, event.url.origin);\n } catch {\n console.error(\n '[intlayer-proxy] URL rewrite failed — invalid path:',\n rewrittenPath\n );\n }\n }\n\n // Forward any Set-Cookie or custom headers set by setLocaleInStorageServer to\n // the h3 v2 response object so they are included in the outgoing HTTP response.\n // Accessing event.res lazily creates the H3EventResponse (no cost if empty).\n if (Object.keys(accumulatedHeaders).length > 0) {\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n event.res.headers.set(key, value);\n }\n }\n\n resolve(undefined);\n });\n });\n"],"mappings":";;;;;;AAwCA,MAAM,iBAAiB,iBAAiB;AACzB,aAAa,cAKvB,CAAC,CAAC,0BAA0B,KAAK,GAAG,EACvC,OAAO,OACT,CAAC;AAED,MAAM,iBAAiB,2BAA2B;;;;;;;;;;;;;;;;;;;;AAqBlD,mCAAe,OAAO,UACpB,IAAI,SAA0B,YAAY;CACxC,MAAM,cAAc,MAAM;;;;;;;;;;;;;;;;CAiB1B,MAAM,UAAU;EACd,KAAK;EACL,QAAQ;EACR,SAAS;GACP,QAAQ,MAAM,QAAQ,IAAI,QAAQ,KAAK;GACvC,MAAM,MAAM,QAAQ,IAAI,MAAM,KAAK;GACnC,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB,KAAK;GAC3D,oBAAoB,MAAM,QAAQ,IAAI,kBAAkB,KAAK;GAC7D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;GAC/D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;EACjE;CACF;CAEA,IAAI,qBAAqB;CACzB,MAAM,qBAA6C,CAAC;;;;;;;;;;;CAYpD,MAAM,UAAU;EACd,UACE,YACA,YACA;GAEA,qBAAqB;GACrB,IAAI,cAAc,OAAO,eAAe,UACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAClD,mBAAmB,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IACtD,MAAM,MAAM,KACb,OAAO,KAAK;GAGpB,OAAO;EACT;EACA,UAAU,MAAc,OAAmC;GAEzD,mBAAmB,KAAK,YAAY,KAAK,MAAM,QAAQ,KAAK,IACvD,MAAM,MAAM,KACb,OAAO,KAAK;GAChB,OAAO;EACT;EACA,UAAU,MAAc;GACtB,OAAO,mBAAmB,KAAK,YAAY;EAC7C;EACA,aAAa;GACX,OAAO,EAAE,GAAG,mBAAmB;EACjC;EACA,IAAI,MAA+B;GAGjC,MAAM,aAAa,IAAI,QAAQ;GAC/B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,WAAW,IAAI,KAAK,KAAK;GAI3B,QACE,IAAI,SAFJ,sBAAsB,OAAO,qBAAqB,MAGnC,OAAO,OAAO,SAAS,WAAW,OAAO,MACtD;IACE,QAAQ;IACR,SAAS;GACX,CACF,CACF;GACA,OAAO;EACT;EACA,aAAa;CACf;CAEA,eAAe,SAAS,eAAe;EAErC,MAAM,gBAAgB,QAAQ;EAE9B,IAAI,kBAAkB,aAQpB,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,eAAe,MAAM,IAAI,MAAM;EACrD,QAAQ;GACN,QAAQ,MACN,uDACA,aACF;EACF;EAMF,IAAI,OAAO,KAAK,kBAAkB,CAAC,CAAC,SAAS,GAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;EAIpC,QAAQ,MAAS;CACnB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"intlayerNitroHandler.mjs","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\n/**\n * The configuration as a plain JSON object.\n *\n * Bundlers alias `@intlayer/config/built` to the generated\n * `.intlayer/config/configuration.mjs`, which inlines the resolved\n * configuration at build time. Loading it through `@intlayer/config/node`\n * instead would transpile `intlayer.config.ts` with `esbuild` on every server\n * start, making the built server unable to boot without `node_modules` next to\n * it - which is exactly what a Nitro deployment output is.\n */\nimport * as builtConfiguration from '@intlayer/config/built';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport { formatProxyEnabledMessage } from '@intlayer/core/localization';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createProxyHandler } from './intlayerProxyHandler';\n\n/**\n * Minimal duck-type for h3 v2's H3Event.\n *\n * We intentionally avoid importing from 'h3' to keep this file runtime-agnostic —\n * Nitro bundles h3 internally and provides the populated event at runtime. Using a\n * structural type here means the file compiles without h3 in devDependencies and\n * works with any h3 v2-compatible runtime (Bun, Deno, Node).\n */\ntype H3EventLike = {\n /**\n * pathname + search — a computed getter on H3Event:\n * `return this.url.pathname + this.url.search`\n */\n readonly path: string;\n /**\n * Full URL object — a **plain property** (not a getter) on H3Event, safe to\n * replace for internal URL rewrites. After assignment, `event.path` will\n * automatically reflect the new pathname + search via the getter.\n */\n url: URL;\n /**\n * Web Fetch API Headers — always populated in h3 v2 regardless of preset\n * (Node, Bun, Deno). Use `.get(name)` instead of bracket-access.\n */\n readonly headers: Headers;\n /**\n * Lazy response object — created on first access; its `headers` carry outgoing\n * response headers (e.g. Set-Cookie) that h3 merges into the HTTP response.\n */\n readonly res: {\n readonly headers: Headers;\n };\n};\n\nconst intlayerConfig = builtConfiguration as unknown as IntlayerConfig;\nconst logger = getAppLogger(intlayerConfig);\n// A Nitro server is a production server, so the stored locale always drives\n// redirects here — hence the `false`. This runs once per server process; the\n// Vite preview server deliberately does not add a second proxy layer on top of\n// this one (see `configurePreviewServer` in `intlayerProxyPlugin`).\nlogger(formatProxyEnabledMessage(false), {\n level: 'info',\n});\n\nconst nodeMiddleware = createProxyHandler({ configuration: intlayerConfig });\n\n/**\n * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).\n *\n * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event\n * model exclusively and is therefore compatible with ALL Nitro presets — including Bun\n * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with\n * \"undefined is not an object (evaluating 'event.node.req')\".\n *\n * It bridges h3 v2 events to the Node.js-style proxy middleware\n * via lightweight IncomingMessage / ServerResponse shims:\n *\n * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends\n * the correct HTTP response to the browser.\n * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten\n * URL so `event.path` (a getter) returns the new pathname for downstream handlers and\n * the Nitro router.\n * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to\n * the next handler / route.\n */\nexport default async (event: H3EventLike): Promise<Response | void> =>\n new Promise<Response | void>((resolve) => {\n const initialPath = event.path;\n\n /**\n * Minimal IncomingMessage shim.\n *\n * Only the fields actually read by the proxy middleware are populated:\n * - url : the current pathname + search, modified for rewrites\n * - headers.cookie : locale cookie detection\n * - headers.host : domain-based locale routing\n * - headers.accept-language : browser Accept-Language fallback\n * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups\n * - headers.x-nitro-prerender : marks a request issued by Nitro's\n * prerenderer, which keeps the generated page URL-driven\n *\n * headers must be a mutable plain object because setLocaleInStorageServer\n * writes Set-Cookie back via req.headers[name] = value.\n */\n const fakeReq = {\n url: initialPath,\n method: 'GET',\n headers: {\n cookie: event.headers.get('cookie') ?? '',\n host: event.headers.get('host') ?? '',\n 'accept-language': event.headers.get('accept-language') ?? '',\n 'x-forwarded-host': event.headers.get('x-forwarded-host') ?? '',\n 'x-forwarded-proto': event.headers.get('x-forwarded-proto') ?? '',\n 'x-nitro-prerender': event.headers.get('x-nitro-prerender') ?? '',\n } as Record<string, string>,\n } as unknown as IncomingMessage;\n\n let responseStatusCode = 200;\n const accumulatedHeaders: Record<string, string> = {};\n\n /**\n * Minimal ServerResponse shim.\n *\n * Implements only the methods that the proxy middleware invokes:\n * writeHead() — status + Location header for 301 redirects\n * setHeader() — Set-Cookie written by setLocaleInStorageServer\n * getHeader() — defensive read-back (not strictly required but safe)\n * end() — finalises the response; for redirects this returns a\n * Web API Response object that Nitro sends to the client\n */\n const fakeRes = {\n writeHead(\n statusCode: number,\n headersArg?: Record<string, string | string[] | number> | string\n ) {\n // Capture the status code and any headers supplied alongside writeHead.\n responseStatusCode = statusCode;\n if (headersArg && typeof headersArg === 'object') {\n for (const [key, value] of Object.entries(headersArg)) {\n accumulatedHeaders[key.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n }\n }\n return fakeRes;\n },\n setHeader(name: string, value: string | number | string[]) {\n // Capture Set-Cookie and other outgoing headers.\n accumulatedHeaders[name.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n return fakeRes;\n },\n getHeader(name: string) {\n return accumulatedHeaders[name.toLowerCase()];\n },\n getHeaders() {\n return { ...accumulatedHeaders };\n },\n end(body?: string | Buffer | null) {\n // Build a Web API Response from accumulated status + headers + body.\n // For 3xx redirects the body is intentionally null.\n const webHeaders = new Headers();\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n webHeaders.set(key, value);\n }\n const isRedirect =\n responseStatusCode >= 300 && responseStatusCode < 400;\n resolve(\n new Response(\n isRedirect ? null : typeof body === 'string' ? body : null,\n {\n status: responseStatusCode,\n headers: webHeaders,\n }\n )\n );\n return fakeRes;\n },\n headersSent: false,\n } as unknown as ServerResponse<IncomingMessage>;\n\n nodeMiddleware(fakeReq, fakeRes, () => {\n // Middleware called next() — either a URL rewrite or a true pass-through.\n const rewrittenPath = fakeReq.url as string;\n\n if (rewrittenPath !== initialPath) {\n // The middleware rewrote the URL (e.g. /about → /en/about for locale prefix).\n // Replace event.url so that event.path (the getter: url.pathname + url.search)\n // returns the new path and the Nitro router matches the correct route.\n //\n // event.url is a plain property on h3 v2's H3Event (not a getter), so direct\n // assignment is safe. We use event.url.origin as the base so relative paths\n // resolve correctly; for path-only requests origin defaults to http://localhost.\n try {\n event.url = new URL(rewrittenPath, event.url.origin);\n } catch {\n console.error(\n '[intlayer-proxy] URL rewrite failed — invalid path:',\n rewrittenPath\n );\n }\n }\n\n // Forward any Set-Cookie or custom headers set by setLocaleInStorageServer to\n // the h3 v2 response object so they are included in the outgoing HTTP response.\n // Accessing event.res lazily creates the H3EventResponse (no cost if empty).\n if (Object.keys(accumulatedHeaders).length > 0) {\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n event.res.headers.set(key, value);\n }\n }\n\n resolve(undefined);\n });\n });\n"],"mappings":";;;;;;;;;;;;;;;;AAmDA,MAAM,iBAAiB;AACR,aAAa,cAKvB,CAAC,CAAC,0BAA0B,KAAK,GAAG,EACvC,OAAO,OACT,CAAC;AAED,MAAM,iBAAiB,mBAAmB,EAAE,eAAe,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;AAqB3E,mCAAe,OAAO,UACpB,IAAI,SAA0B,YAAY;CACxC,MAAM,cAAc,MAAM;;;;;;;;;;;;;;;;CAiB1B,MAAM,UAAU;EACd,KAAK;EACL,QAAQ;EACR,SAAS;GACP,QAAQ,MAAM,QAAQ,IAAI,QAAQ,KAAK;GACvC,MAAM,MAAM,QAAQ,IAAI,MAAM,KAAK;GACnC,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB,KAAK;GAC3D,oBAAoB,MAAM,QAAQ,IAAI,kBAAkB,KAAK;GAC7D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;GAC/D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;EACjE;CACF;CAEA,IAAI,qBAAqB;CACzB,MAAM,qBAA6C,CAAC;;;;;;;;;;;CAYpD,MAAM,UAAU;EACd,UACE,YACA,YACA;GAEA,qBAAqB;GACrB,IAAI,cAAc,OAAO,eAAe,UACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAClD,mBAAmB,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IACtD,MAAM,MAAM,KACb,OAAO,KAAK;GAGpB,OAAO;EACT;EACA,UAAU,MAAc,OAAmC;GAEzD,mBAAmB,KAAK,YAAY,KAAK,MAAM,QAAQ,KAAK,IACvD,MAAM,MAAM,KACb,OAAO,KAAK;GAChB,OAAO;EACT;EACA,UAAU,MAAc;GACtB,OAAO,mBAAmB,KAAK,YAAY;EAC7C;EACA,aAAa;GACX,OAAO,EAAE,GAAG,mBAAmB;EACjC;EACA,IAAI,MAA+B;GAGjC,MAAM,aAAa,IAAI,QAAQ;GAC/B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,WAAW,IAAI,KAAK,KAAK;GAI3B,QACE,IAAI,SAFJ,sBAAsB,OAAO,qBAAqB,MAGnC,OAAO,OAAO,SAAS,WAAW,OAAO,MACtD;IACE,QAAQ;IACR,SAAS;GACX,CACF,CACF;GACA,OAAO;EACT;EACA,aAAa;CACf;CAEA,eAAe,SAAS,eAAe;EAErC,MAAM,gBAAgB,QAAQ;EAE9B,IAAI,kBAAkB,aAQpB,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,eAAe,MAAM,IAAI,MAAM;EACrD,QAAQ;GACN,QAAQ,MACN,uDACA,aACF;EACF;EAMF,IAAI,OAAO,KAAK,kBAAkB,CAAC,CAAC,SAAS,GAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;EAIpC,QAAQ,MAAS;CACnB,CAAC;AACH,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import { intlayerCompiler } from "./IntlayerCompilerPlugin.mjs";
2
2
  import { intlayerMinify } from "./intlayerMinifyPlugin.mjs";
3
3
  import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
4
+ import { intlayerChunk } from "./intlayerChunkPlugin.mjs";
4
5
  import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
6
+ import { intlayerPreload } from "./intlayerPreloadPlugin.mjs";
5
7
  import { intlayerProxy } from "./intlayerProxyPlugin.mjs";
6
8
  import { resolve } from "node:path";
7
9
  import { createPruneContext } from "@intlayer/babel";
@@ -104,6 +106,8 @@ const intlayerPlugin = (configOptions) => {
104
106
  plugins.push(intlayerOptimize(intlayerConfig, pruneContext, compatCallers));
105
107
  plugins.push(intlayerPrune(intlayerConfig, pruneContext));
106
108
  plugins.push(intlayerMinify(intlayerConfig, pruneContext));
109
+ plugins.push(intlayerChunk(intlayerConfig));
110
+ plugins.push(intlayerPreload(intlayerConfig));
107
111
  if (intlayerConfig.compiler?.enabled && intlayerConfig.compiler?.output) plugins.push(intlayerCompiler({ configOptions: getConfigOptions }));
108
112
  if (resolveProxyMode(intlayerConfig.routing.enableProxy) !== "disabled") plugins.push(intlayerProxy({
109
113
  ...proxy,
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { type CompatCallerConfig, createPruneContext } from '@intlayer/babel';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatDictionarySelectorEnvVar,\n formatNodeTypeToEnvVar,\n formatOptimizedNestingEnvVar,\n getConfigEnvVars,\n} from '@intlayer/config/envVars';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getAlias,\n getHasDictionarySelector,\n getUnusedNodeTypesAsync,\n} from '@intlayer/config/utils';\nimport { resolveProxyMode } from '@intlayer/core/localization';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { logConfigDetails } from '@intlayer/engine/logConfigDetails';\nimport type { PluginOption } from 'vite';\nimport { intlayerCompiler } from './IntlayerCompilerPlugin';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport {\n type IntlayerProxyPluginOptions,\n intlayerProxy,\n} from './intlayerProxyPlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\n\n/**\n * Extended options accepted by the intlayer Vite plugin.\n *\n * Extends {@link GetConfigurationOptions} with compat-adapter caller\n * configurations. Compat adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) inject their own caller configs here so the\n * field-usage analyser can recognise their translation function call patterns\n * and prune unused dictionary fields accordingly.\n */\nexport type IntlayerPluginOptions = GetConfigurationOptions & {\n /**\n * Compat-adapter namespace caller configurations to pass to the\n * field-usage analyser (Vite `buildStart` phase).\n *\n * Defined by each compat adapter package; the core `vite-intlayer` package\n * ships with an empty default to stay framework-agnostic.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Options forwarded to the bundled locale-routing proxy (`intlayerProxy`).\n *\n * Since Intlayer v9 the proxy is plugged directly into the main `intlayer()`\n * plugin (controlled by `routing.enableProxy`, unset by default, which\n * selects auto mode). These routing options let you tune that bundled proxy\n * without registering it separately — e.g. ignore API routes from locale\n * routing.\n *\n * @example\n * ```ts\n * intlayer({ proxy: { ignore: (req) => req.url?.startsWith('/api') } })\n * ```\n */\n proxy?: Pick<IntlayerProxyPluginOptions, 'ignore'>;\n};\n\n/**\n * Vite plugin that integrates Intlayer into the Vite build process.\n *\n * It handles:\n * 1. Preparing Intlayer resources (dictionaries) before build.\n * 2. Configuring Vite aliases for dictionary access.\n * 3. Setting up dev-server watchers for content changes.\n * 4. Applying build optimizations (tree-shaking dictionaries).\n *\n * @param configOptions - Optional configuration to override default Intlayer settings.\n * @returns A Vite plugin option.\n *\n * @example\n * ```ts\n * import { intlayer } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayer()],\n * });\n *\n * ```\n * @deprecated Rename to intlayer instead\n */\nexport const intlayerPlugin = (\n configOptions?: IntlayerPluginOptions\n): PluginOption => {\n const { compatCallers, proxy, ...getConfigOptions } = configOptions ?? {};\n const intlayerConfig = getConfiguration(getConfigOptions);\n logConfigDetails(getConfigOptions);\n const appLogger = getAppLogger(intlayerConfig);\n\n const alias = getAlias({\n configuration: intlayerConfig,\n formatter: (value: string) => resolve(value),\n });\n\n const aliasPackages = Object.keys(alias);\n\n const plugins: PluginOption[] = [\n {\n name: 'vite-intlayer-plugin',\n\n apply: (_config, env) => {\n // Don't apply intlayer plugin during `preview` command\n const isPreviewCommand =\n env.command === 'serve' && env.mode === 'production';\n\n // But if liveSync is enabled, ensure the data are fresh\n const isLiveSyncEnabled = intlayerConfig.editor.liveSync;\n\n return !isPreviewCommand || isLiveSyncEnabled;\n },\n\n config: async (_config, env) => {\n const { mode } = intlayerConfig.build;\n\n const isDevCommand =\n env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Only call prepareIntlayer during `dev` or `build` (not during `preview`)\n // If prod: clean and rebuild once\n // If dev: rebuild only once if it's more than 1 hour since last rebuild\n if (isDevCommand || isBuildCommand || mode === 'auto') {\n // Lazily load the heavy build pipeline (`@intlayer/engine/build`) only\n // when a rebuild is actually required, keeping the Vite config phase\n // lightweight (e.g. during `preview`).\n const { prepareIntlayer } = await import('@intlayer/engine/build');\n\n // prepareIntlayer use runOnce to ensure to run only once because will run twice on client and server side otherwise\n await prepareIntlayer(intlayerConfig, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build (to ensure to rebuild all dictionaries)\n : 1000 * 60 * 60, // 1 hour for dev (default cache timeout)\n env: isBuildCommand ? 'prod' : 'dev',\n });\n }\n\n let define: Record<string, string> = {\n // Preset an env var to avoid 'process is not defined' error\n // Needed for some libraries that does not add process.env\n 'process.env.INTLAYER': '\"true\"',\n };\n\n if (isBuildCommand) {\n const dictionaries = getDictionaries(intlayerConfig);\n\n // Mirrors the optimize plugin's own `apply` condition: the local\n // `nest()` resolver may only be selected when that transform runs,\n // since it is the transform that injects the nest attachment.\n const isOptimizeEnabled = intlayerConfig.build.optimize !== false;\n\n if (Object.keys(dictionaries).length === 0) {\n appLogger(\n 'No dictionaries found. Please check your configuration.',\n {\n isVerbose: true,\n }\n );\n }\n\n const unusedNodeTypes = await getUnusedNodeTypesAsync(dictionaries);\n\n if (unusedNodeTypes.length > 0) {\n appLogger(\n [\n 'Filtering out unused logic:',\n unusedNodeTypes\n .filter(\n (key) =>\n !['reactNode', 'solidNode', 'preactNode'].includes(key)\n )\n .map((key) => colorize(key, BLUE))\n .join(', '),\n ],\n {\n isVerbose: true,\n }\n );\n }\n\n define = {\n ...define,\n\n // Tree shacking env var based on config\n ...formatNodeTypeToEnvVar(\n unusedNodeTypes,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Selects the local `nest()` resolver, which reads the nest\n // targets attached to each dictionary by the optimize transform\n // instead of the registry that transform empties.\n ...formatOptimizedNestingEnvVar(\n isOptimizeEnabled,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var for the dictionary selector logic\n // (collections / variants)\n ...formatDictionarySelectorEnvVar(\n getHasDictionarySelector(dictionaries),\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var based on config\n ...getConfigEnvVars(\n intlayerConfig,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"` // Wrap by \"\" to ensure env var set properly\n ),\n };\n }\n\n // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return {\n define,\n resolve: {\n alias,\n },\n optimizeDeps: {\n // Exclude alias entry points since they're local files, not npm packages\n exclude: aliasPackages,\n },\n ssr: {\n // Ensure intlayer packages are bundled so aliases are applied\n noExternal: [/(^@intlayer\\/|intlayer$)/],\n },\n };\n },\n\n configureServer: async (server) => {\n if (server.config.mode === 'development') {\n // Lazily load the file watcher (`@intlayer/engine/watcher`, which pulls\n // in `@parcel/watcher`) only when the dev server actually starts.\n const { watch } = await import('@intlayer/engine/watcher');\n\n // Start watching (assuming watch is also async)\n await watch({ configuration: intlayerConfig });\n }\n },\n },\n ];\n\n // Shared mutable state: the optimize plugin writes field-usage data during\n // buildStart; the prune and minify plugins read it during transform.\n const pruneContext = createPruneContext();\n\n // Babel transform: rewrites useIntlayer/getIntlayer calls and injects\n // JSON / dynamic-mjs imports. Also runs the usage analyser in buildStart.\n plugins.push(intlayerOptimize(intlayerConfig, pruneContext, compatCallers));\n\n // Prune: removes unused content fields from dictionary JSON files.\n // Runs with enforce:'pre' so it intercepts raw JSON before Vite's\n // built-in JSON → ESM conversion.\n plugins.push(intlayerPrune(intlayerConfig, pruneContext));\n\n // Minify: compacts dictionary JSON files (parse + re-stringify).\n // Registered after prune so it receives already-pruned output when both options are active.\n plugins.push(intlayerMinify(intlayerConfig, pruneContext));\n\n // Compiler: extracts content declared inline in components into dictionaries.\n // Bundled directly into the main plugin so users no longer need to register\n // `intlayerCompiler()` separately. Only added when the compiler is enabled and\n // an `output` path is configured. Registering `intlayerCompiler()` manually as\n // well is safe — the compiler deduplicates itself.\n if (intlayerConfig.compiler?.enabled && intlayerConfig.compiler?.output) {\n plugins.push(intlayerCompiler({ configOptions: getConfigOptions }));\n }\n\n // Proxy: locale-routing middleware (detection / redirect / rewrite) for dev,\n // preview, and production SSR. Bundled directly into the main plugin and\n // controlled by the `routing.enableProxy` option, which is unset by default\n // and therefore resolves to auto mode. Only an explicit `false` skips\n // registration — a plain truthiness check would wrongly drop the proxy for\n // every project that never set the option.\n // Registering `intlayerProxy()` manually as well is safe — the proxy\n // deduplicates itself.\n if (resolveProxyMode(intlayerConfig.routing.enableProxy) !== 'disabled') {\n plugins.push(intlayerProxy({ ...proxy, configOptions: getConfigOptions }));\n }\n\n return plugins;\n};\n\n/**\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intlayer = intlayerPlugin;\n/**\n * @deprecated Rename to intlayer instead\n *\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intLayerPlugin = intlayerPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FA,MAAa,kBACX,kBACiB;CACjB,MAAM,EAAE,eAAe,OAAO,GAAG,qBAAqB,iBAAiB,CAAC;CACxE,MAAM,iBAAiB,iBAAiB,gBAAgB;CACxD,iBAAiB,gBAAgB;CACjC,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,QAAQ,SAAS;EACrB,eAAe;EACf,YAAY,UAAkB,QAAQ,KAAK;CAC7C,CAAC;CAED,MAAM,gBAAgB,OAAO,KAAK,KAAK;CAEvC,MAAM,UAA0B,CAC9B;EACE,MAAM;EAEN,QAAQ,SAAS,QAAQ;GAEvB,MAAM,mBACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAG1C,MAAM,oBAAoB,eAAe,OAAO;GAEhD,OAAO,CAAC,oBAAoB;EAC9B;EAEA,QAAQ,OAAO,SAAS,QAAQ;GAC9B,MAAM,EAAE,SAAS,eAAe;GAEhC,MAAM,eACJ,IAAI,YAAY,WAAW,IAAI,SAAS;GAC1C,MAAM,iBAAiB,IAAI,YAAY;GAKvC,IAAI,gBAAgB,kBAAkB,SAAS,QAAQ;IAIrD,MAAM,EAAE,oBAAoB,MAAM,OAAO;IAGzC,MAAM,gBAAgB,gBAAgB;KACpC,OAAO;KACP,gBAAgB,iBACZ,MACA;KACJ,KAAK,iBAAiB,SAAS;IACjC,CAAC;GACH;GAEA,IAAI,SAAiC,EAGnC,wBAAwB,WAC1B;GAEA,IAAI,gBAAgB;IAClB,MAAM,eAAe,gBAAgB,cAAc;IAKnD,MAAM,oBAAoB,eAAe,MAAM,aAAa;IAE5D,IAAI,OAAO,KAAK,YAAY,CAAC,CAAC,WAAW,GACvC,UACE,2DACA,EACE,WAAW,KACb,CACF;IAGF,MAAM,kBAAkB,MAAM,wBAAwB,YAAY;IAElE,IAAI,gBAAgB,SAAS,GAC3B,UACE,CACE,+BACA,gBACG,QACE,QACC,CAAC;KAAC;KAAa;KAAa;IAAY,CAAC,CAAC,SAAS,GAAG,CAC1D,CAAC,CACA,KAAK,QAAQ,SAAS,KAAK,IAAI,CAAC,CAAC,CACjC,KAAK,IAAI,CACd,GACA,EACE,WAAW,KACb,CACF;IAGF,SAAS;KACP,GAAG;KAGH,GAAG,uBACD,kBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAKA,GAAG,6BACD,oBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAIA,GAAG,+BACD,yBAAyB,YAAY,IACpC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;KAGA,GAAG,iBACD,iBACC,QAAQ,eAAe,QACvB,UAAU,IAAI,MAAM,EACvB;IACF;GACF;GAIA,OAAO;IACL;IACA,SAAS,EACP,MACF;IACA,cAAc,EAEZ,SAAS,cACX;IACA,KAAK,EAEH,YAAY,CAAC,0BAA0B,EACzC;GACF;EACF;EAEA,iBAAiB,OAAO,WAAW;GACjC,IAAI,OAAO,OAAO,SAAS,eAAe;IAGxC,MAAM,EAAE,UAAU,MAAM,OAAO;IAG/B,MAAM,MAAM,EAAE,eAAe,eAAe,CAAC;GAC/C;EACF;CACF,CACF;CAIA,MAAM,eAAe,mBAAmB;CAIxC,QAAQ,KAAK,iBAAiB,gBAAgB,cAAc,aAAa,CAAC;CAK1E,QAAQ,KAAK,cAAc,gBAAgB,YAAY,CAAC;CAIxD,QAAQ,KAAK,eAAe,gBAAgB,YAAY,CAAC;CAOzD,IAAI,eAAe,UAAU,WAAW,eAAe,UAAU,QAC/D,QAAQ,KAAK,iBAAiB,EAAE,eAAe,iBAAiB,CAAC,CAAC;CAWpE,IAAI,iBAAiB,eAAe,QAAQ,WAAW,MAAM,YAC3D,QAAQ,KAAK,cAAc;EAAE,GAAG;EAAO,eAAe;CAAiB,CAAC,CAAC;CAG3E,OAAO;AACT;;;;;;;;;;;AAYA,MAAa,WAAW;;;;;;;;;;;;;AAaxB,MAAa,iBAAiB"}
1
+ {"version":3,"file":"intlayerPlugin.mjs","names":[],"sources":["../../src/intlayerPlugin.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { type CompatCallerConfig, createPruneContext } from '@intlayer/babel';\nimport { BLUE } from '@intlayer/config/colors';\nimport {\n formatDictionarySelectorEnvVar,\n formatNodeTypeToEnvVar,\n formatOptimizedNestingEnvVar,\n getConfigEnvVars,\n} from '@intlayer/config/envVars';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n getAlias,\n getHasDictionarySelector,\n getUnusedNodeTypesAsync,\n} from '@intlayer/config/utils';\nimport { resolveProxyMode } from '@intlayer/core/localization';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { logConfigDetails } from '@intlayer/engine/logConfigDetails';\nimport type { PluginOption } from 'vite';\nimport { intlayerCompiler } from './IntlayerCompilerPlugin';\nimport { intlayerChunk } from './intlayerChunkPlugin';\nimport { intlayerMinify } from './intlayerMinifyPlugin';\nimport { intlayerOptimize } from './intlayerOptimizePlugin';\nimport { intlayerPreload } from './intlayerPreloadPlugin';\nimport {\n type IntlayerProxyPluginOptions,\n intlayerProxy,\n} from './intlayerProxyPlugin';\nimport { intlayerPrune } from './intlayerPrunePlugin';\n\n/**\n * Extended options accepted by the intlayer Vite plugin.\n *\n * Extends {@link GetConfigurationOptions} with compat-adapter caller\n * configurations. Compat adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) inject their own caller configs here so the\n * field-usage analyser can recognise their translation function call patterns\n * and prune unused dictionary fields accordingly.\n */\nexport type IntlayerPluginOptions = GetConfigurationOptions & {\n /**\n * Compat-adapter namespace caller configurations to pass to the\n * field-usage analyser (Vite `buildStart` phase).\n *\n * Defined by each compat adapter package; the core `vite-intlayer` package\n * ships with an empty default to stay framework-agnostic.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Options forwarded to the bundled locale-routing proxy (`intlayerProxy`).\n *\n * Since Intlayer v9 the proxy is plugged directly into the main `intlayer()`\n * plugin (controlled by `routing.enableProxy`, unset by default, which\n * selects auto mode). These routing options let you tune that bundled proxy\n * without registering it separately — e.g. ignore API routes from locale\n * routing.\n *\n * @example\n * ```ts\n * intlayer({ proxy: { ignore: (req) => req.url?.startsWith('/api') } })\n * ```\n */\n proxy?: Pick<IntlayerProxyPluginOptions, 'ignore'>;\n};\n\n/**\n * Vite plugin that integrates Intlayer into the Vite build process.\n *\n * It handles:\n * 1. Preparing Intlayer resources (dictionaries) before build.\n * 2. Configuring Vite aliases for dictionary access.\n * 3. Setting up dev-server watchers for content changes.\n * 4. Applying build optimizations (tree-shaking dictionaries).\n *\n * @param configOptions - Optional configuration to override default Intlayer settings.\n * @returns A Vite plugin option.\n *\n * @example\n * ```ts\n * import { intlayer } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayer()],\n * });\n *\n * ```\n * @deprecated Rename to intlayer instead\n */\nexport const intlayerPlugin = (\n configOptions?: IntlayerPluginOptions\n): PluginOption => {\n const { compatCallers, proxy, ...getConfigOptions } = configOptions ?? {};\n const intlayerConfig = getConfiguration(getConfigOptions);\n logConfigDetails(getConfigOptions);\n const appLogger = getAppLogger(intlayerConfig);\n\n const alias = getAlias({\n configuration: intlayerConfig,\n formatter: (value: string) => resolve(value),\n });\n\n const aliasPackages = Object.keys(alias);\n\n const plugins: PluginOption[] = [\n {\n name: 'vite-intlayer-plugin',\n\n apply: (_config, env) => {\n // Don't apply intlayer plugin during `preview` command\n const isPreviewCommand =\n env.command === 'serve' && env.mode === 'production';\n\n // But if liveSync is enabled, ensure the data are fresh\n const isLiveSyncEnabled = intlayerConfig.editor.liveSync;\n\n return !isPreviewCommand || isLiveSyncEnabled;\n },\n\n config: async (_config, env) => {\n const { mode } = intlayerConfig.build;\n\n const isDevCommand =\n env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Only call prepareIntlayer during `dev` or `build` (not during `preview`)\n // If prod: clean and rebuild once\n // If dev: rebuild only once if it's more than 1 hour since last rebuild\n if (isDevCommand || isBuildCommand || mode === 'auto') {\n // Lazily load the heavy build pipeline (`@intlayer/engine/build`) only\n // when a rebuild is actually required, keeping the Vite config phase\n // lightweight (e.g. during `preview`).\n const { prepareIntlayer } = await import('@intlayer/engine/build');\n\n // prepareIntlayer use runOnce to ensure to run only once because will run twice on client and server side otherwise\n await prepareIntlayer(intlayerConfig, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build (to ensure to rebuild all dictionaries)\n : 1000 * 60 * 60, // 1 hour for dev (default cache timeout)\n env: isBuildCommand ? 'prod' : 'dev',\n });\n }\n\n let define: Record<string, string> = {\n // Preset an env var to avoid 'process is not defined' error\n // Needed for some libraries that does not add process.env\n 'process.env.INTLAYER': '\"true\"',\n };\n\n if (isBuildCommand) {\n const dictionaries = getDictionaries(intlayerConfig);\n\n // Mirrors the optimize plugin's own `apply` condition: the local\n // `nest()` resolver may only be selected when that transform runs,\n // since it is the transform that injects the nest attachment.\n const isOptimizeEnabled = intlayerConfig.build.optimize !== false;\n\n if (Object.keys(dictionaries).length === 0) {\n appLogger(\n 'No dictionaries found. Please check your configuration.',\n {\n isVerbose: true,\n }\n );\n }\n\n const unusedNodeTypes = await getUnusedNodeTypesAsync(dictionaries);\n\n if (unusedNodeTypes.length > 0) {\n appLogger(\n [\n 'Filtering out unused logic:',\n unusedNodeTypes\n .filter(\n (key) =>\n !['reactNode', 'solidNode', 'preactNode'].includes(key)\n )\n .map((key) => colorize(key, BLUE))\n .join(', '),\n ],\n {\n isVerbose: true,\n }\n );\n }\n\n define = {\n ...define,\n\n // Tree shacking env var based on config\n ...formatNodeTypeToEnvVar(\n unusedNodeTypes,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Selects the local `nest()` resolver, which reads the nest\n // targets attached to each dictionary by the optimize transform\n // instead of the registry that transform empties.\n ...formatOptimizedNestingEnvVar(\n isOptimizeEnabled,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var for the dictionary selector logic\n // (collections / variants)\n ...formatDictionarySelectorEnvVar(\n getHasDictionarySelector(dictionaries),\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"`\n ),\n\n // Tree shacking env var based on config\n ...getConfigEnvVars(\n intlayerConfig,\n (key) => `process.env.${key}`,\n (value) => `\"${value}\"` // Wrap by \"\" to ensure env var set properly\n ),\n };\n }\n\n // mergeConfig handles both array and record alias formats,\n // and correctly appends to optimizeDeps.exclude / ssr.noExternal\n return {\n define,\n resolve: {\n alias,\n },\n optimizeDeps: {\n // Exclude alias entry points since they're local files, not npm packages\n exclude: aliasPackages,\n },\n ssr: {\n // Ensure intlayer packages are bundled so aliases are applied\n noExternal: [/(^@intlayer\\/|intlayer$)/],\n },\n };\n },\n\n configureServer: async (server) => {\n if (server.config.mode === 'development') {\n // Lazily load the file watcher (`@intlayer/engine/watcher`, which pulls\n // in `@parcel/watcher`) only when the dev server actually starts.\n const { watch } = await import('@intlayer/engine/watcher');\n\n // Start watching (assuming watch is also async)\n await watch({ configuration: intlayerConfig });\n }\n },\n },\n ];\n\n // Shared mutable state: the optimize plugin writes field-usage data during\n // buildStart; the prune and minify plugins read it during transform.\n const pruneContext = createPruneContext();\n\n // Babel transform: rewrites useIntlayer/getIntlayer calls and injects\n // JSON / dynamic-mjs imports. Also runs the usage analyser in buildStart.\n plugins.push(intlayerOptimize(intlayerConfig, pruneContext, compatCallers));\n\n // Prune: removes unused content fields from dictionary JSON files.\n // Runs with enforce:'pre' so it intercepts raw JSON before Vite's\n // built-in JSON → ESM conversion.\n plugins.push(intlayerPrune(intlayerConfig, pruneContext));\n\n // Minify: compacts dictionary JSON files (parse + re-stringify).\n // Registered after prune so it receives already-pruned output when both options are active.\n plugins.push(intlayerMinify(intlayerConfig, pruneContext));\n\n // Chunk: regroups the per-dictionary chunks that `importMode: 'dynamic'`\n // emits, so each code-split boundary loads its content in one request.\n plugins.push(intlayerChunk(intlayerConfig));\n\n // Preload: makes each dynamic entry point await its locale's dictionary while\n // it evaluates, so the content travels with the chunk that needs it instead\n // of being fetched — and suspended on — once that chunk renders.\n plugins.push(intlayerPreload(intlayerConfig));\n\n // Compiler: extracts content declared inline in components into dictionaries.\n // Bundled directly into the main plugin so users no longer need to register\n // `intlayerCompiler()` separately. Only added when the compiler is enabled and\n // an `output` path is configured. Registering `intlayerCompiler()` manually as\n // well is safe — the compiler deduplicates itself.\n if (intlayerConfig.compiler?.enabled && intlayerConfig.compiler?.output) {\n plugins.push(intlayerCompiler({ configOptions: getConfigOptions }));\n }\n\n // Proxy: locale-routing middleware (detection / redirect / rewrite) for dev,\n // preview, and production SSR. Bundled directly into the main plugin and\n // controlled by the `routing.enableProxy` option, which is unset by default\n // and therefore resolves to auto mode. Only an explicit `false` skips\n // registration — a plain truthiness check would wrongly drop the proxy for\n // every project that never set the option.\n // Registering `intlayerProxy()` manually as well is safe — the proxy\n // deduplicates itself.\n if (resolveProxyMode(intlayerConfig.routing.enableProxy) !== 'disabled') {\n plugins.push(intlayerProxy({ ...proxy, configOptions: getConfigOptions }));\n }\n\n return plugins;\n};\n\n/**\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intlayer = intlayerPlugin;\n/**\n * @deprecated Rename to intlayer instead\n *\n * A Vite plugin that integrates Intlayer configuration into the build process\n *\n * ```ts\n * // Example usage of the plugin in a Vite configuration\n * export default defineConfig({\n * plugins: [ intlayer() ],\n * });\n * ```\n */\nexport const intLayerPlugin = intlayerPlugin;\n"],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { colorizeNumber, getAppLogger } from "@intlayer/config/logger";
2
+ import { normalizePath } from "@intlayer/config/utils";
3
+ import { createRequire } from "node:module";
4
+ import { DYNAMIC_ENTRY_PATTERN, addDynamicEntryPreload, resolvePreloadModuleId } from "@intlayer/config/dictionaryPreload";
5
+
6
+ //#region src/intlayerPreloadPlugin.ts
7
+ /**
8
+ * Absolute id of the locale resolver the injected preamble imports.
9
+ *
10
+ * Resolved from this package, which declares `@intlayer/core` as a dependency,
11
+ * rather than left bare in the emitted source: the importer is a generated
12
+ * entry point under the application's `.intlayer` directory, and an application
13
+ * only depends on `intlayer` and its framework binding, so the bare specifier
14
+ * would resolve from a tree that need not contain the package at all.
15
+ */
16
+ const preloadModuleId = normalizePath(resolvePreloadModuleId(createRequire(import.meta.url)));
17
+ /**
18
+ * Makes dictionaries load with the chunk that needs them, instead of being
19
+ * requested once that chunk renders.
20
+ *
21
+ * In `importMode: 'dynamic'` a component reads its dictionary through a loader
22
+ * map and suspends while the matching chunk is fetched. The fetch is only
23
+ * discovered once the component executes, so it lands at the very end of the
24
+ * request waterfall and every navigation flashes the pending fallback before
25
+ * any text appears — the cost that used to push applications towards
26
+ * `importMode: 'static'`, which inlines every locale into the bundle.
27
+ *
28
+ * Starting that fetch from the generated entry point itself moves it forward to
29
+ * module-evaluation time: it leaves with the chunk that needs it — on a
30
+ * router's `import()` of a route, including a preload on hover — instead of
31
+ * waiting for the consuming component to execute. By the time the read happens
32
+ * the content is usually already there and the render is synchronous.
33
+ *
34
+ * The fetch is deliberately not awaited at the top level. Doing so would make
35
+ * the entry point an async module and hold up every chunk that imports it,
36
+ * which permanently strands `React.lazy` boundaries resolving during hydration
37
+ * on their fallback — see `addDynamicEntryPreload`.
38
+ *
39
+ * Complements the grouping in `intlayerChunk`: grouping decides how many
40
+ * requests a boundary makes, this decides when they start.
41
+ *
42
+ * @param intlayerConfig - Resolved Intlayer configuration.
43
+ */
44
+ const intlayerPreload = (intlayerConfig) => {
45
+ const dynamicDictionariesDir = normalizePath(intlayerConfig.system.dynamicDictionariesDir);
46
+ const appLogger = getAppLogger(intlayerConfig);
47
+ /** Entry points that received the preamble, for the build summary. */
48
+ const preloadedKeys = /* @__PURE__ */ new Set();
49
+ return {
50
+ name: "vite-intlayer-preload-plugin",
51
+ apply: () => intlayerConfig.build.dictionariesPreload,
52
+ enforce: "pre",
53
+ transform(code, moduleId, options) {
54
+ if (options?.ssr) return null;
55
+ const posixId = normalizePath(moduleId.split("?", 1)[0] ?? moduleId);
56
+ if (!posixId.startsWith(dynamicDictionariesDir)) return null;
57
+ const key = DYNAMIC_ENTRY_PATTERN.exec(posixId)?.groups?.key;
58
+ if (!key) return null;
59
+ const result = addDynamicEntryPreload(code, preloadModuleId);
60
+ if (result.skipped) {
61
+ if (result.skipped === "unrecognized-shape") appLogger(`Dictionary entry point ${key} has an unexpected shape and was not preloaded.`, {
62
+ level: "warn",
63
+ isVerbose: true
64
+ });
65
+ return null;
66
+ }
67
+ preloadedKeys.add(key);
68
+ return {
69
+ code: result.code,
70
+ map: null
71
+ };
72
+ },
73
+ /**
74
+ * Reports what the injection actually did. Silence means no entry point
75
+ * matched — usually an `importMode` that emits none, or dictionaries that
76
+ * are all qualified.
77
+ */
78
+ closeBundle() {
79
+ if (preloadedKeys.size === 0) return;
80
+ appLogger(`Preloaded ${colorizeNumber(preloadedKeys.size)} dictionary entry points into their consuming chunks.`);
81
+ preloadedKeys.clear();
82
+ }
83
+ };
84
+ };
85
+
86
+ //#endregion
87
+ export { intlayerPreload };
88
+ //# sourceMappingURL=intlayerPreloadPlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerPreloadPlugin.mjs","names":[],"sources":["../../src/intlayerPreloadPlugin.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport {\n addDynamicEntryPreload,\n DYNAMIC_ENTRY_PATTERN,\n resolvePreloadModuleId,\n} from '@intlayer/config/dictionaryPreload';\nimport { colorizeNumber, getAppLogger } from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Plugin } from 'vite';\n\n/**\n * Absolute id of the locale resolver the injected preamble imports.\n *\n * Resolved from this package, which declares `@intlayer/core` as a dependency,\n * rather than left bare in the emitted source: the importer is a generated\n * entry point under the application's `.intlayer` directory, and an application\n * only depends on `intlayer` and its framework binding, so the bare specifier\n * would resolve from a tree that need not contain the package at all.\n */\nconst preloadModuleId = normalizePath(\n resolvePreloadModuleId(createRequire(import.meta.url))\n);\n\n/**\n * Makes dictionaries load with the chunk that needs them, instead of being\n * requested once that chunk renders.\n *\n * In `importMode: 'dynamic'` a component reads its dictionary through a loader\n * map and suspends while the matching chunk is fetched. The fetch is only\n * discovered once the component executes, so it lands at the very end of the\n * request waterfall and every navigation flashes the pending fallback before\n * any text appears — the cost that used to push applications towards\n * `importMode: 'static'`, which inlines every locale into the bundle.\n *\n * Starting that fetch from the generated entry point itself moves it forward to\n * module-evaluation time: it leaves with the chunk that needs it — on a\n * router's `import()` of a route, including a preload on hover — instead of\n * waiting for the consuming component to execute. By the time the read happens\n * the content is usually already there and the render is synchronous.\n *\n * The fetch is deliberately not awaited at the top level. Doing so would make\n * the entry point an async module and hold up every chunk that imports it,\n * which permanently strands `React.lazy` boundaries resolving during hydration\n * on their fallback — see `addDynamicEntryPreload`.\n *\n * Complements the grouping in `intlayerChunk`: grouping decides how many\n * requests a boundary makes, this decides when they start.\n *\n * @param intlayerConfig - Resolved Intlayer configuration.\n */\nexport const intlayerPreload = (intlayerConfig: IntlayerConfig): Plugin => {\n const dynamicDictionariesDir = normalizePath(\n intlayerConfig.system.dynamicDictionariesDir\n );\n\n const appLogger = getAppLogger(intlayerConfig);\n\n /** Entry points that received the preamble, for the build summary. */\n const preloadedKeys = new Set<string>();\n\n return {\n name: 'vite-intlayer-preload-plugin',\n\n apply: () => intlayerConfig.build.dictionariesPreload,\n\n // Runs before the optimize plugin's `post`-enforced transform, which only\n // rewrites component sources and dictionary list entries — never these\n // generated per-key entry points — so the two never touch the same module.\n enforce: 'pre',\n\n // Only the client suspends on a dictionary, and only the browser can\n // resolve a browsing locale. Transforming the server build too would make\n // its modules async for a branch that never runs there.\n //\n // Not gated on the configured `importMode`: a `.content` file can set\n // `importMode: 'dynamic'` on a single dictionary under a `static` or\n // `fetch` global mode, and that dictionary suspends exactly like a fully\n // dynamic one. Only entry points the optimizer actually imported reach this\n // hook, so membership of the module graph is the accurate gate.\n transform(code, moduleId, options) {\n if (options?.ssr) return null;\n\n const posixId = normalizePath(moduleId.split('?', 1)[0] ?? moduleId);\n if (!posixId.startsWith(dynamicDictionariesDir)) return null;\n\n const key = DYNAMIC_ENTRY_PATTERN.exec(posixId)?.groups?.key;\n if (!key) return null;\n\n const result = addDynamicEntryPreload(code, preloadModuleId);\n\n if (result.skipped) {\n if (result.skipped === 'unrecognized-shape') {\n appLogger(\n `Dictionary entry point ${key} has an unexpected shape and was not preloaded.`,\n { level: 'warn', isVerbose: true }\n );\n }\n return null;\n }\n\n preloadedKeys.add(key);\n\n return { code: result.code, map: null };\n },\n\n /**\n * Reports what the injection actually did. Silence means no entry point\n * matched — usually an `importMode` that emits none, or dictionaries that\n * are all qualified.\n */\n closeBundle() {\n if (preloadedKeys.size === 0) return;\n\n appLogger(\n `Preloaded ${colorizeNumber(preloadedKeys.size)} dictionary entry points into their consuming chunks.`\n );\n\n preloadedKeys.clear();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,MAAM,kBAAkB,cACtB,uBAAuB,cAAc,YAAY,GAAG,CAAC,CACvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,mBAAmB,mBAA2C;CACzE,MAAM,yBAAyB,cAC7B,eAAe,OAAO,sBACxB;CAEA,MAAM,YAAY,aAAa,cAAc;;CAG7C,MAAM,gCAAgB,IAAI,IAAY;CAEtC,OAAO;EACL,MAAM;EAEN,aAAa,eAAe,MAAM;EAKlC,SAAS;EAWT,UAAU,MAAM,UAAU,SAAS;GACjC,IAAI,SAAS,KAAK,OAAO;GAEzB,MAAM,UAAU,cAAc,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,QAAQ;GACnE,IAAI,CAAC,QAAQ,WAAW,sBAAsB,GAAG,OAAO;GAExD,MAAM,MAAM,sBAAsB,KAAK,OAAO,CAAC,EAAE,QAAQ;GACzD,IAAI,CAAC,KAAK,OAAO;GAEjB,MAAM,SAAS,uBAAuB,MAAM,eAAe;GAE3D,IAAI,OAAO,SAAS;IAClB,IAAI,OAAO,YAAY,sBACrB,UACE,0BAA0B,IAAI,kDAC9B;KAAE,OAAO;KAAQ,WAAW;IAAK,CACnC;IAEF,OAAO;GACT;GAEA,cAAc,IAAI,GAAG;GAErB,OAAO;IAAE,MAAM,OAAO;IAAM,KAAK;GAAK;EACxC;;;;;;EAOA,cAAc;GACZ,IAAI,cAAc,SAAS,GAAG;GAE9B,UACE,aAAa,eAAe,cAAc,IAAI,EAAE,sDAClD;GAEA,cAAc,MAAM;EACtB;CACF;AACF"}