vite-intlayer 8.6.10 → 8.7.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/intlayerMinifyPlugin.mjs +148 -0
- package/dist/esm/intlayerMinifyPlugin.mjs.map +1 -0
- package/dist/esm/intlayerOptimizePlugin.mjs +220 -71
- package/dist/esm/intlayerOptimizePlugin.mjs.map +1 -1
- package/dist/esm/intlayerPlugin.mjs +7 -1
- package/dist/esm/intlayerPlugin.mjs.map +1 -1
- package/dist/esm/intlayerProxyPlugin.mjs.map +1 -1
- package/dist/esm/intlayerPrunePlugin.mjs +230 -0
- package/dist/esm/intlayerPrunePlugin.mjs.map +1 -0
- package/dist/esm/pruneContext.mjs +15 -0
- package/dist/esm/pruneContext.mjs.map +1 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/intlayerMinifyPlugin.d.ts +41 -0
- package/dist/types/intlayerMinifyPlugin.d.ts.map +1 -0
- package/dist/types/intlayerOptimizePlugin.d.ts +24 -1
- package/dist/types/intlayerOptimizePlugin.d.ts.map +1 -1
- package/dist/types/intlayerPlugin.d.ts.map +1 -1
- package/dist/types/intlayerPrunePlugin.d.ts +36 -0
- package/dist/types/intlayerPrunePlugin.d.ts.map +1 -0
- package/dist/types/pruneContext.d.ts +8 -0
- package/dist/types/pruneContext.d.ts.map +1 -0
- package/package.json +16 -11
package/dist/esm/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { intlayerCompiler } from "./IntlayerCompilerPlugin.mjs";
|
|
2
|
+
import { intlayerMinify } from "./intlayerMinifyPlugin.mjs";
|
|
3
|
+
import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
|
|
2
4
|
import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
|
|
3
5
|
import { intLayerPlugin, intlayer, intlayerPlugin } from "./intlayerPlugin.mjs";
|
|
4
6
|
import { intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.mjs";
|
|
5
7
|
|
|
6
|
-
export { intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerOptimize, intlayerPlugin, intlayerProxy };
|
|
8
|
+
export { intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerProxy, intlayerPrune };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import * as ANSIColors from "@intlayer/config/colors";
|
|
3
|
+
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
4
|
+
import { formatPath, runOnce } from "@intlayer/chokidar/utils";
|
|
5
|
+
|
|
6
|
+
//#region src/intlayerMinifyPlugin.ts
|
|
7
|
+
/**
|
|
8
|
+
* Recursively renames user-defined keys in a compiled intlayer content value
|
|
9
|
+
* using the provided `NestedRenameMap`.
|
|
10
|
+
*
|
|
11
|
+
* Traversal rules (mirrors `buildNestedRenameMapFromContent`):
|
|
12
|
+
* - Object with `nodeType: 'translation'` → intlayer translation node.
|
|
13
|
+
* Keys at this level (`nodeType`, `translation`) are NOT renamed.
|
|
14
|
+
* Recurse into each per-locale value with the same rename map.
|
|
15
|
+
* - Object without `nodeType` → user-defined record.
|
|
16
|
+
* Rename its keys using the current rename map level, then recurse into
|
|
17
|
+
* each value with that entry's `children` map.
|
|
18
|
+
* - Primitive / array → returned as-is.
|
|
19
|
+
*/
|
|
20
|
+
const renameContentRecursively = (value, renameMap) => {
|
|
21
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
22
|
+
const record = value;
|
|
23
|
+
if (typeof record.nodeType === "string" && record.translation && typeof record.translation === "object" && !Array.isArray(record.translation)) {
|
|
24
|
+
const renamedTranslation = {};
|
|
25
|
+
for (const [locale, localeValue] of Object.entries(record.translation)) renamedTranslation[locale] = renameContentRecursively(localeValue, renameMap);
|
|
26
|
+
return {
|
|
27
|
+
...record,
|
|
28
|
+
translation: renamedTranslation
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const [key, val] of Object.entries(record)) {
|
|
33
|
+
const renameEntry = renameMap.get(key);
|
|
34
|
+
if (renameEntry) result[renameEntry.shortName] = renameContentRecursively(val, renameEntry.children);
|
|
35
|
+
else result[key] = val;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Applies the nested field rename map to a parsed dictionary object and
|
|
41
|
+
* returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)
|
|
42
|
+
* are never touched; only keys inside `content` are renamed.
|
|
43
|
+
*/
|
|
44
|
+
const applyFieldRenameToDict = (dict, renameMap) => {
|
|
45
|
+
const content = dict.content;
|
|
46
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) return dict;
|
|
47
|
+
return {
|
|
48
|
+
...dict,
|
|
49
|
+
content: renameContentRecursively(content, renameMap)
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Returns the Vite plugin that minifies compiled dictionary JSON files by
|
|
54
|
+
* removing all unnecessary whitespace and optionally renaming content field
|
|
55
|
+
* names to short alphabetic aliases (a, b, c, …).
|
|
56
|
+
*
|
|
57
|
+
* Targets:
|
|
58
|
+
* - `<dictionariesDir>/**\/*.json` – static all-locale dictionaries
|
|
59
|
+
* - `<dynamicDictionariesDir>/**\/*.json` – per-locale dynamic dictionaries
|
|
60
|
+
* - `<fetchDictionariesDir>/**\/*.json` – per-locale fetch dictionaries
|
|
61
|
+
*
|
|
62
|
+
* The plugin is deliberately independent of the prune plugin: it can run
|
|
63
|
+
* on its own when only `build.minify` is enabled. When both `purge` and
|
|
64
|
+
* `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`
|
|
65
|
+
* and is registered before this one); this plugin then receives the already-
|
|
66
|
+
* pruned JSON, renames its field keys, and compacts it.
|
|
67
|
+
*
|
|
68
|
+
* Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:
|
|
69
|
+
* those dictionaries encountered a structural issue during the prune phase
|
|
70
|
+
* and should be left completely untouched to avoid shipping broken data.
|
|
71
|
+
*
|
|
72
|
+
* Field renaming (property mangling) is applied only for dictionaries that
|
|
73
|
+
* have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.
|
|
74
|
+
* The corresponding rename is also applied to source-file property accesses by
|
|
75
|
+
* the babel rename pass inside `intlayerOptimize`. Internal intlayer fields
|
|
76
|
+
* such as `nodeType` are never renamed.
|
|
77
|
+
*
|
|
78
|
+
* @param intlayerConfig - Resolved intlayer configuration.
|
|
79
|
+
* @param pruneContext - Optional shared state from the prune plugin. When
|
|
80
|
+
* provided, dictionaries flagged as edge-cases are
|
|
81
|
+
* skipped and field renames are applied. Pass `null`
|
|
82
|
+
* if the prune plugin is not active.
|
|
83
|
+
*/
|
|
84
|
+
const intlayerMinify = (intlayerConfig, pruneContext) => {
|
|
85
|
+
const logger = getAppLogger(intlayerConfig);
|
|
86
|
+
const { optimize, minify } = intlayerConfig.build;
|
|
87
|
+
const editorEnabled = intlayerConfig.editor.enabled;
|
|
88
|
+
const { dictionariesDir, dynamicDictionariesDir, baseDir } = intlayerConfig.system;
|
|
89
|
+
const isDictionaryJsonFile = (absoluteFilePath) => absoluteFilePath.endsWith(".json") && (absoluteFilePath.startsWith(dictionariesDir) || absoluteFilePath.startsWith(dynamicDictionariesDir));
|
|
90
|
+
const isMinifyEnabled = (_config, env) => {
|
|
91
|
+
const isBuildCommand = env.command === "build";
|
|
92
|
+
if (!(optimize === void 0 && isBuildCommand || optimize === true)) return false;
|
|
93
|
+
if (!minify) return false;
|
|
94
|
+
if (!isBuildCommand) return false;
|
|
95
|
+
if (editorEnabled) {
|
|
96
|
+
runOnce(join(baseDir, ".intlayer", "cache", "intlayer-minify-editor-warning.lock"), () => logger([
|
|
97
|
+
"Dictionary minification is",
|
|
98
|
+
colorize("disabled", ANSIColors.GREY_DARK),
|
|
99
|
+
"because",
|
|
100
|
+
colorize("editor.enabled", ANSIColors.BLUE),
|
|
101
|
+
"is",
|
|
102
|
+
colorize("true", ANSIColors.GREY_DARK),
|
|
103
|
+
"— the editor requires full dictionary content."
|
|
104
|
+
]), { cacheTimeoutMs: 1e3 * 10 });
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
runOnce(join(baseDir, ".intlayer", "cache", "intlayer-minify-plugin-enabled.lock"), () => logger(["Dictionary minification", colorize("enabled", ANSIColors.GREEN)]), { cacheTimeoutMs: 1e3 * 10 });
|
|
108
|
+
return true;
|
|
109
|
+
};
|
|
110
|
+
return [{
|
|
111
|
+
name: "vite-intlayer-dictionary-minify",
|
|
112
|
+
enforce: "pre",
|
|
113
|
+
apply: isMinifyEnabled,
|
|
114
|
+
transform: (rawJsonCode, moduleId) => {
|
|
115
|
+
const absoluteFilePath = moduleId.split("?", 1)[0];
|
|
116
|
+
if (!isDictionaryJsonFile(absoluteFilePath)) return null;
|
|
117
|
+
let parsedDict;
|
|
118
|
+
try {
|
|
119
|
+
parsedDict = JSON.parse(rawJsonCode);
|
|
120
|
+
} catch (parseError) {
|
|
121
|
+
logger([
|
|
122
|
+
`Could not parse`,
|
|
123
|
+
formatPath(absoluteFilePath),
|
|
124
|
+
`as JSON. Skipping minification for this file.`,
|
|
125
|
+
parseError instanceof Error ? `(${parseError.message})` : String(parseError)
|
|
126
|
+
], { level: "warn" });
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const dictionaryKey = typeof parsedDict.key === "string" ? parsedDict.key : void 0;
|
|
130
|
+
if (pruneContext && dictionaryKey && pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return null;
|
|
131
|
+
if (pruneContext && dictionaryKey) {
|
|
132
|
+
const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
|
|
133
|
+
if (fieldRenameMap && fieldRenameMap.size > 0) parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
code: JSON.stringify({
|
|
137
|
+
key: parsedDict.key,
|
|
138
|
+
content: parsedDict.content
|
|
139
|
+
}),
|
|
140
|
+
map: null
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { intlayerMinify };
|
|
148
|
+
//# sourceMappingURL=intlayerMinifyPlugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intlayerMinifyPlugin.mjs","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { NestedRenameMap, PruneContext } from '@intlayer/babel';\nimport { formatPath, runOnce } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\n// Field-rename helper\n\n/**\n * Recursively renames user-defined keys in a compiled intlayer content value\n * using the provided `NestedRenameMap`.\n *\n * Traversal rules (mirrors `buildNestedRenameMapFromContent`):\n * - Object with `nodeType: 'translation'` → intlayer translation node.\n * Keys at this level (`nodeType`, `translation`) are NOT renamed.\n * Recurse into each per-locale value with the same rename map.\n * - Object without `nodeType` → user-defined record.\n * Rename its keys using the current rename map level, then recurse into\n * each value with that entry's `children` map.\n * - Primitive / array → returned as-is.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale with the same rename map\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val; // key not in map – keep as-is (e.g. already-pruned)\n }\n }\n return result;\n};\n\n/**\n * Applies the nested field rename map to a parsed dictionary object and\n * returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)\n * are never touched; only keys inside `content` are renamed.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// Plugin\n\n/**\n * Returns the Vite plugin that minifies compiled dictionary JSON files by\n * removing all unnecessary whitespace and optionally renaming content field\n * names to short alphabetic aliases (a, b, c, …).\n *\n * Targets:\n * - `<dictionariesDir>/**\\/*.json` – static all-locale dictionaries\n * - `<dynamicDictionariesDir>/**\\/*.json` – per-locale dynamic dictionaries\n * - `<fetchDictionariesDir>/**\\/*.json` – per-locale fetch dictionaries\n *\n * The plugin is deliberately independent of the prune plugin: it can run\n * on its own when only `build.minify` is enabled. When both `purge` and\n * `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`\n * and is registered before this one); this plugin then receives the already-\n * pruned JSON, renames its field keys, and compacts it.\n *\n * Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:\n * those dictionaries encountered a structural issue during the prune phase\n * and should be left completely untouched to avoid shipping broken data.\n *\n * Field renaming (property mangling) is applied only for dictionaries that\n * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.\n * The corresponding rename is also applied to source-file property accesses by\n * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields\n * such as `nodeType` are never renamed.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Optional shared state from the prune plugin. When\n * provided, dictionaries flagged as edge-cases are\n * skipped and field renames are applied. Pass `null`\n * if the prune plugin is not active.\n */\nexport const intlayerMinify = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const { dictionariesDir, dynamicDictionariesDir, baseDir } =\n intlayerConfig.system;\n\n // Fetch-mode dictionaries are served from a remote API at runtime using their\n // original field names. Minifying them (renaming fields) would create a\n // mismatch between the server response and the renamed client-side accesses.\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir));\n\n const isMinifyEnabled = (\n _config: unknown,\n env: { command: string }\n ): boolean => {\n const isBuildCommand = env.command === 'build';\n const isOptimizeActive =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isOptimizeActive) return false;\n if (!minify) return false;\n if (!isBuildCommand) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary minification is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n '— the editor requires full dictionary content.',\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n return false;\n }\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-plugin-enabled.lock'\n ),\n () =>\n logger([\n 'Dictionary minification',\n colorize('enabled', ANSIColors.GREEN),\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n };\n\n const minifyPlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-minify',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM\n // conversion. Declaration order in the plugin array ensures this runs\n // after the prune plugin (which is also 'pre' but registered earlier).\n enforce: 'pre',\n apply: isMinifyEnabled,\n\n transform: (rawJsonCode, moduleId) => {\n const absoluteFilePath = moduleId.split('?', 1)[0];\n\n if (!isDictionaryJsonFile(absoluteFilePath)) return null;\n\n // Parse JSON\n let parsedDict: Record<string, unknown>;\n try {\n parsedDict = JSON.parse(rawJsonCode) as Record<string, unknown>;\n } catch (parseError) {\n logger(\n [\n `Could not parse`,\n formatPath(absoluteFilePath),\n `as JSON. Skipping minification for this file.`,\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n const dictionaryKey =\n typeof parsedDict.key === 'string' ? parsedDict.key : undefined;\n\n // Skip edge-case dictionaries\n if (\n pruneContext &&\n dictionaryKey &&\n pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)\n ) {\n return null; // structural issue flagged during prune – leave untouched\n }\n\n // Apply field rename (property mangling)\n if (pruneContext && dictionaryKey) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);\n }\n }\n\n // Strip all top-level metadata – ship only key + content\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n content: parsedDict.content,\n }),\n map: null,\n };\n },\n };\n\n return [minifyPlugin];\n};\n"],"mappings":"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"}
|
|
@@ -1,65 +1,230 @@
|
|
|
1
1
|
import { intlayerVueAsyncPlugin } from "./intlayerVueAsyncPlugin.mjs";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
+
import { INTLAYER_USAGE_REGEX, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, optimizeSourceFile, renameFieldsInSourceFile } from "@intlayer/babel";
|
|
4
5
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
5
|
-
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
6
|
+
import { colorize, colorizeKey, getAppLogger } from "@intlayer/config/logger";
|
|
7
|
+
import { buildComponentFilesList, formatPath, runOnce } from "@intlayer/chokidar/utils";
|
|
6
8
|
import { getDictionaries } from "@intlayer/dictionaries-entry";
|
|
7
|
-
import { transformAsync } from "@babel/core";
|
|
8
|
-
import { buildComponentFilesList, runOnce } from "@intlayer/chokidar/utils";
|
|
9
9
|
import { IMPORT_MODE } from "@intlayer/config/defaultValues";
|
|
10
10
|
|
|
11
11
|
//#region src/intlayerOptimizePlugin.ts
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Returns the Vite plugins responsible for the build optimisation step.
|
|
14
|
+
*
|
|
15
|
+
* Contains three internal plugins:
|
|
16
|
+
*
|
|
17
|
+
* 1. Vue async plugin – handles Vue SFC async script blocks.
|
|
18
|
+
* 2. Usage analyser (`vite-intlayer-usage-analyzer`) – pre-scans every
|
|
19
|
+
* component source file during `buildStart` to build the field-usage map
|
|
20
|
+
* in `pruneContext`. This runs before any `transform` calls so the
|
|
21
|
+
* downstream prune plugin always has complete data.
|
|
22
|
+
* 3. Babel transform (`vite-intlayer-babel-transform`) – rewrites
|
|
23
|
+
* `useIntlayer('key')` / `getIntlayer('key')` calls into
|
|
24
|
+
* `useDictionary(_hash)` / `getDictionary(_hash)` and injects the
|
|
25
|
+
* corresponding JSON (or dynamic `.mjs`) imports. Also applies field-name
|
|
26
|
+
* renaming when `build.minify` is enabled.
|
|
27
|
+
*
|
|
28
|
+
* @param intlayerConfig - Resolved intlayer configuration.
|
|
29
|
+
* @param pruneContext - Shared mutable state written here and read by the
|
|
30
|
+
* prune and minify plugins. Pass `null` to skip
|
|
31
|
+
* analysis (e.g. when both `purge` and `minify` are
|
|
32
|
+
* disabled).
|
|
33
|
+
*/
|
|
34
|
+
const intlayerOptimize = async (intlayerConfig, pruneContext) => {
|
|
14
35
|
try {
|
|
15
36
|
const logger = getAppLogger(intlayerConfig);
|
|
16
|
-
const { optimize } = intlayerConfig.build;
|
|
37
|
+
const { optimize, purge, minify } = intlayerConfig.build;
|
|
38
|
+
const editorEnabled = intlayerConfig.editor.enabled;
|
|
17
39
|
const importMode = intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;
|
|
18
|
-
const { dictionariesDir, dynamicDictionariesDir, unmergedDictionariesDir, fetchDictionariesDir, mainDir } = intlayerConfig.system;
|
|
19
|
-
const { baseDir } = intlayerConfig.system;
|
|
40
|
+
const { dictionariesDir, dynamicDictionariesDir, unmergedDictionariesDir, fetchDictionariesDir, mainDir, baseDir } = intlayerConfig.system;
|
|
20
41
|
const dictionariesEntryPath = join(mainDir, "dictionaries.mjs");
|
|
21
42
|
const unmergedDictionariesEntryPath = join(mainDir, "unmerged_dictionaries.mjs");
|
|
22
43
|
const dynamicDictionariesEntryPath = join(mainDir, "dynamic_dictionaries.mjs");
|
|
23
|
-
const
|
|
24
|
-
|
|
44
|
+
const componentFilesList = buildComponentFilesList(intlayerConfig);
|
|
45
|
+
const transformableFilesList = [
|
|
46
|
+
...componentFilesList,
|
|
25
47
|
dictionariesEntryPath,
|
|
26
48
|
unmergedDictionariesEntryPath
|
|
27
49
|
];
|
|
28
50
|
const dictionaries = getDictionaries(intlayerConfig);
|
|
29
|
-
const
|
|
51
|
+
const dictionaryKeyToImportModeMap = {};
|
|
30
52
|
Object.values(dictionaries).forEach((dictionary) => {
|
|
31
|
-
|
|
53
|
+
dictionaryKeyToImportModeMap[dictionary.key] = dictionary.importMode ?? importMode ?? IMPORT_MODE;
|
|
32
54
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
const isBuildOptimizeEnabled = (_config, env) => {
|
|
56
|
+
const isBuildCommand = env.command === "build";
|
|
57
|
+
return optimize === void 0 && isBuildCommand || optimize === true;
|
|
58
|
+
};
|
|
59
|
+
const isAnalysisEnabled = (_config, env) => !editorEnabled && (!!purge || !!minify) && isBuildOptimizeEnabled(_config, env);
|
|
60
|
+
return [
|
|
61
|
+
intlayerVueAsyncPlugin(intlayerConfig, transformableFilesList),
|
|
62
|
+
{
|
|
63
|
+
name: "vite-intlayer-usage-analyzer",
|
|
64
|
+
enforce: "pre",
|
|
65
|
+
apply: isAnalysisEnabled,
|
|
66
|
+
buildStart: async () => {
|
|
67
|
+
if (!pruneContext) return;
|
|
68
|
+
await Promise.all(componentFilesList.map(async (sourceFilePath) => {
|
|
69
|
+
if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return;
|
|
70
|
+
let sourceCode;
|
|
71
|
+
try {
|
|
72
|
+
sourceCode = await readFile(sourceFilePath, "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!INTLAYER_USAGE_REGEX.test(sourceCode)) return;
|
|
77
|
+
try {
|
|
78
|
+
await analyzeFieldUsageInFile(sourceFilePath, sourceCode, pruneContext);
|
|
79
|
+
} catch (parseError) {
|
|
80
|
+
pruneContext.hasUnparsableSourceFiles = true;
|
|
81
|
+
logger([
|
|
82
|
+
`Could not parse`,
|
|
83
|
+
formatPath(sourceFilePath),
|
|
84
|
+
`for field-usage analysis.`,
|
|
85
|
+
"Dictionaries whose usage cannot be confirmed will not be pruned.",
|
|
86
|
+
parseError instanceof Error ? `(${parseError.message})` : String(parseError)
|
|
87
|
+
], { level: "warn" });
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
if (pruneContext.pendingFrameworkAnalysis.size > 0) {
|
|
91
|
+
const vuePending = /* @__PURE__ */ new Map();
|
|
92
|
+
const sveltePending = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const [filePath, entries] of pruneContext.pendingFrameworkAnalysis) if (filePath.endsWith(".vue")) vuePending.set(filePath, entries);
|
|
94
|
+
else if (filePath.endsWith(".svelte")) sveltePending.set(filePath, entries);
|
|
95
|
+
/** Merge framework-extracted field usage into pruneContext. */
|
|
96
|
+
const mergeFrameworkResult = (dictionaryKey, fields) => {
|
|
97
|
+
if (fields && fields.size > 0) {
|
|
98
|
+
pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);
|
|
99
|
+
const existing = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
|
|
100
|
+
if (existing === "all") return;
|
|
101
|
+
const merged = existing instanceof Set ? new Set([...existing, ...fields]) : new Set(fields);
|
|
102
|
+
pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, merged);
|
|
103
|
+
} else pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, "all");
|
|
104
|
+
};
|
|
105
|
+
if (vuePending.size > 0) {
|
|
106
|
+
let extractVueIntlayerFieldUsage = null;
|
|
107
|
+
try {
|
|
108
|
+
extractVueIntlayerFieldUsage = (await import("@intlayer/vue-compiler")).extractVueIntlayerFieldUsage;
|
|
109
|
+
} catch {}
|
|
110
|
+
for (const [filePath, entries] of vuePending) {
|
|
111
|
+
if (!extractVueIntlayerFieldUsage) {
|
|
112
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, void 0);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
let fileCode;
|
|
116
|
+
try {
|
|
117
|
+
fileCode = await readFile(filePath, "utf-8");
|
|
118
|
+
} catch {
|
|
119
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, void 0);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const result = extractVueIntlayerFieldUsage(fileCode, entries);
|
|
123
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, result.get(dictionaryKey));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (sveltePending.size > 0) {
|
|
127
|
+
let extractSvelteIntlayerFieldUsage = null;
|
|
128
|
+
try {
|
|
129
|
+
extractSvelteIntlayerFieldUsage = (await import("@intlayer/svelte-compiler")).extractSvelteIntlayerFieldUsage;
|
|
130
|
+
} catch {}
|
|
131
|
+
for (const [filePath, entries] of sveltePending) {
|
|
132
|
+
if (!extractSvelteIntlayerFieldUsage) {
|
|
133
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, void 0);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
let fileCode;
|
|
137
|
+
try {
|
|
138
|
+
fileCode = await readFile(filePath, "utf-8");
|
|
139
|
+
} catch {
|
|
140
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, void 0);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const result = extractSvelteIntlayerFieldUsage(fileCode, entries);
|
|
144
|
+
for (const { dictionaryKey } of entries) mergeFrameworkResult(dictionaryKey, result.get(dictionaryKey));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const [dictionaryKey, sourceFilePaths] of pruneContext.dictionaryKeysWithUntrackedBindings) logger([
|
|
149
|
+
`Dictionary`,
|
|
150
|
+
colorizeKey(dictionaryKey),
|
|
151
|
+
`cannot be purged or minified.`,
|
|
152
|
+
`\n Reason: the result of`,
|
|
153
|
+
`${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(`'${dictionaryKey}'`)}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,
|
|
154
|
+
`is assigned to a plain variable in:`,
|
|
155
|
+
...sourceFilePaths.map((filePath) => `\n - ${formatPath(filePath)}`)
|
|
156
|
+
], { level: "warn" });
|
|
157
|
+
if (minify) for (const [dictionaryKey, fieldUsage] of pruneContext.dictionaryKeyToFieldUsageMap) {
|
|
158
|
+
if (fieldUsage === "all") continue;
|
|
159
|
+
if (dictionaryKeyToImportModeMap[dictionaryKey] === "fetch") continue;
|
|
160
|
+
if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey)) continue;
|
|
161
|
+
let dictionaryContent = null;
|
|
162
|
+
const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);
|
|
163
|
+
try {
|
|
164
|
+
const raw = await readFile(staticJsonPath, "utf-8");
|
|
165
|
+
dictionaryContent = JSON.parse(raw).content;
|
|
166
|
+
} catch {
|
|
167
|
+
try {
|
|
168
|
+
const dynamicDir = join(dynamicDictionariesDir, dictionaryKey);
|
|
169
|
+
const firstJsonFile = (await readdir(dynamicDir)).find((f) => f.endsWith(".json"));
|
|
170
|
+
if (firstJsonFile) {
|
|
171
|
+
const raw = await readFile(join(dynamicDir, firstJsonFile), "utf-8");
|
|
172
|
+
dictionaryContent = JSON.parse(raw).content;
|
|
173
|
+
}
|
|
174
|
+
} catch {}
|
|
175
|
+
}
|
|
176
|
+
if (!dictionaryContent) continue;
|
|
177
|
+
const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent, fieldUsage);
|
|
178
|
+
const opaqueFieldMap = pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey);
|
|
179
|
+
if (opaqueFieldMap) {
|
|
180
|
+
const dangerousEntries = [...opaqueFieldMap.entries()].filter(([fieldName]) => (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0);
|
|
181
|
+
if (dangerousEntries.length > 0) {
|
|
182
|
+
logger([
|
|
183
|
+
`Dictionary`,
|
|
184
|
+
colorizeKey(dictionaryKey),
|
|
185
|
+
`cannot be minified.`,
|
|
186
|
+
...dangerousEntries.flatMap(([fieldName, locations]) => [
|
|
187
|
+
`\n Reason: field`,
|
|
188
|
+
colorize(`'${fieldName}'`, ANSIColors.BLUE),
|
|
189
|
+
`is passed opaquely to a child component: nested keys cannot be safely renamed.`,
|
|
190
|
+
...locations.map((loc) => `\n at ${formatPath(loc)}`),
|
|
191
|
+
`Fix: import useIntlayer directly in the component where it is used.`
|
|
192
|
+
])
|
|
193
|
+
], { level: "warn" });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (nestedRenameMap.size > 0) pruneContext.dictionaryKeyToFieldRenameMap.set(dictionaryKey, nestedRenameMap);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
46
200
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
201
|
+
{
|
|
202
|
+
name: "vite-intlayer-babel-transform",
|
|
203
|
+
enforce: "post",
|
|
204
|
+
apply: (_config, env) => {
|
|
205
|
+
const isBuildCommand = env.command === "build";
|
|
206
|
+
if (!isBuildCommand || !(optimize === void 0 && isBuildCommand || optimize === true)) return false;
|
|
207
|
+
runOnce(join(baseDir, ".intlayer", "cache", "intlayer-optimize-plugin-enabled.lock"), () => logger([
|
|
208
|
+
`Build optimization ${colorize("enabled", ANSIColors.GREEN)}`,
|
|
209
|
+
colorize("(import mode:", ANSIColors.GREY_DARK),
|
|
210
|
+
colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),
|
|
211
|
+
colorize(")", ANSIColors.GREY_DARK)
|
|
212
|
+
]), { cacheTimeoutMs: 1e3 * 10 });
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
transform: async (sourceCode, moduleId) => {
|
|
216
|
+
const sourceFilePath = moduleId.split("?", 1)[0];
|
|
217
|
+
if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;
|
|
218
|
+
if (!transformableFilesList.includes(sourceFilePath)) return null;
|
|
219
|
+
const isDictionaryEntryFile = [dictionariesEntryPath, unmergedDictionariesEntryPath].includes(sourceFilePath);
|
|
220
|
+
const isUsingIntlayer = INTLAYER_USAGE_REGEX.test(sourceCode);
|
|
221
|
+
if (!isUsingIntlayer && !isDictionaryEntryFile) return null;
|
|
222
|
+
let codeToOptimize = sourceCode;
|
|
223
|
+
if (pruneContext && isUsingIntlayer) {
|
|
224
|
+
const renamedCode = await renameFieldsInSourceFile(sourceFilePath, sourceCode, pruneContext);
|
|
225
|
+
if (renamedCode) codeToOptimize = renamedCode;
|
|
226
|
+
}
|
|
227
|
+
const transformResult = await optimizeSourceFile(codeToOptimize, sourceFilePath, {
|
|
63
228
|
optimize,
|
|
64
229
|
dictionariesDir,
|
|
65
230
|
dictionariesEntryPath,
|
|
@@ -68,38 +233,22 @@ const intlayerOptimize = async (intlayerConfig) => {
|
|
|
68
233
|
dynamicDictionariesDir,
|
|
69
234
|
dynamicDictionariesEntryPath,
|
|
70
235
|
fetchDictionariesDir,
|
|
236
|
+
fetchDictionariesEntryPath: join(mainDir, "fetch_dictionaries.mjs"),
|
|
71
237
|
importMode,
|
|
72
|
-
filesList,
|
|
238
|
+
filesList: transformableFilesList,
|
|
73
239
|
replaceDictionaryEntry: true,
|
|
74
|
-
dictionaryModeMap
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"decorators-legacy",
|
|
83
|
-
"classProperties",
|
|
84
|
-
"objectRestSpread",
|
|
85
|
-
"asyncGenerators",
|
|
86
|
-
"functionBind",
|
|
87
|
-
"exportDefaultFrom",
|
|
88
|
-
"exportNamespaceFrom",
|
|
89
|
-
"dynamicImport",
|
|
90
|
-
"nullishCoalescingOperator",
|
|
91
|
-
"optionalChaining"
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
if (result?.code) return {
|
|
96
|
-
code: result.code,
|
|
97
|
-
map: result.map
|
|
98
|
-
};
|
|
240
|
+
dictionaryModeMap: dictionaryKeyToImportModeMap
|
|
241
|
+
});
|
|
242
|
+
if (!transformResult) return null;
|
|
243
|
+
return {
|
|
244
|
+
code: transformResult.code,
|
|
245
|
+
map: transformResult.map
|
|
246
|
+
};
|
|
247
|
+
}
|
|
99
248
|
}
|
|
100
|
-
|
|
101
|
-
} catch (
|
|
102
|
-
console.warn("Failed to
|
|
249
|
+
];
|
|
250
|
+
} catch (pluginInitError) {
|
|
251
|
+
console.warn("[vite-intlayer] Failed to initialise optimization plugin:", pluginInitError);
|
|
103
252
|
return [];
|
|
104
253
|
}
|
|
105
254
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { transformAsync } from '@babel/core';\nimport { intlayerOptimizeBabelPlugin } from '@intlayer/babel';\nimport { buildComponentFilesList, runOnce } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport { colorize, 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 () =>\n logger([\n 'Build optimization enabled',\n colorize(`(import mode:`, ANSIColors.GREY_DARK),\n colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),\n colorize(`)`, ANSIColors.GREY_DARK),\n ]),\n {\n cacheTimeoutMs: 1000 * 10, // 10 seconds\n }\n );\n }\n\n return isEnabled;\n },\n async 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 = await transformAsync(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":";;;;;;;;;;;AAaA,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,QAEC,OAAO;KACL;KACA,SAAS,iBAAiB,WAAW,UAAU;KAC/C,SAAS,cAAc,aAAa,WAAW,KAAK;KACpD,SAAS,KAAK,WAAW,UAAU;KACpC,CAAC,EACJ,EACE,gBAAgB,MAAO,IACxB,CACF;AAGH,WAAO;;GAET,MAAM,UAAU,MAAM,IAAI;;;;;;;;;IASxB,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,MAAM,eAAe,MAAM;KACxC;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
|
+
{"version":3,"file":"intlayerOptimizePlugin.mjs","names":[],"sources":["../../src/intlayerOptimizePlugin.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n analyzeFieldUsageInFile,\n buildNestedRenameMapFromContent,\n INTLAYER_USAGE_REGEX,\n optimizeSourceFile,\n type PruneContext,\n renameFieldsInSourceFile,\n SOURCE_FILE_REGEX,\n} from '@intlayer/babel';\nimport {\n buildComponentFilesList,\n formatPath,\n runOnce,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { IMPORT_MODE } from '@intlayer/config/defaultValues';\nimport { colorize, colorizeKey, 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\n// Plugin\n\n/**\n * Returns the Vite plugins responsible for the build optimisation step.\n *\n * Contains three internal plugins:\n *\n * 1. Vue async plugin – handles Vue SFC async script blocks.\n * 2. Usage analyser (`vite-intlayer-usage-analyzer`) – pre-scans every\n * component source file during `buildStart` to build the field-usage map\n * in `pruneContext`. This runs before any `transform` calls so the\n * downstream prune plugin always has complete data.\n * 3. Babel transform (`vite-intlayer-babel-transform`) – rewrites\n * `useIntlayer('key')` / `getIntlayer('key')` calls into\n * `useDictionary(_hash)` / `getDictionary(_hash)` and injects the\n * corresponding JSON (or dynamic `.mjs`) imports. Also applies field-name\n * renaming when `build.minify` is enabled.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Shared mutable state written here and read by the\n * prune and minify plugins. Pass `null` to skip\n * analysis (e.g. when both `purge` and `minify` are\n * disabled).\n */\nexport const intlayerOptimize = async (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): Promise<PluginOption[]> => {\n try {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, purge, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const importMode =\n intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n unmergedDictionariesDir,\n fetchDictionariesDir,\n mainDir,\n baseDir,\n } = intlayerConfig.system;\n\n const dictionariesEntryPath = join(mainDir, 'dictionaries.mjs');\n const unmergedDictionariesEntryPath = join(\n mainDir,\n 'unmerged_dictionaries.mjs'\n );\n const dynamicDictionariesEntryPath = join(\n mainDir,\n 'dynamic_dictionaries.mjs'\n );\n\n const componentFilesList = buildComponentFilesList(intlayerConfig);\n\n const transformableFilesList = [\n ...componentFilesList,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ];\n\n const dictionaries = getDictionaries(intlayerConfig);\n\n const dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch'\n > = {};\n (Object.values(dictionaries) as Dictionary[]).forEach((dictionary) => {\n dictionaryKeyToImportModeMap[dictionary.key] =\n dictionary.importMode ?? importMode ?? IMPORT_MODE;\n });\n\n const isBuildOptimizeEnabled = (\n _config: unknown,\n env: { command: string }\n ) => {\n const isBuildCommand = env.command === 'build';\n return (optimize === undefined && isBuildCommand) || optimize === true;\n };\n\n const isAnalysisEnabled = (_config: unknown, env: { command: string }) =>\n !editorEnabled &&\n (!!purge || !!minify) &&\n isBuildOptimizeEnabled(_config, env);\n\n return [\n intlayerVueAsyncPlugin(intlayerConfig, transformableFilesList),\n\n // Plugin 1: Usage analyser\n {\n name: 'vite-intlayer-usage-analyzer',\n enforce: 'pre',\n apply: isAnalysisEnabled,\n\n buildStart: async () => {\n if (!pruneContext) return;\n\n // Phase 1: Babel-based field-usage analysis for all component files\n await Promise.all(\n componentFilesList.map(async (sourceFilePath) => {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return;\n\n let sourceCode: string;\n try {\n sourceCode = await readFile(sourceFilePath, 'utf-8');\n } catch {\n return; // unreadable file – skip silently\n }\n\n if (!INTLAYER_USAGE_REGEX.test(sourceCode)) return;\n\n // For Vue/Svelte SFCs, the usage analyzer expects the raw script\n // content. `analyzeFieldUsageInFile` handles block extraction\n // internally via `extractScriptBlocks`.\n try {\n await analyzeFieldUsageInFile(\n sourceFilePath,\n sourceCode,\n pruneContext\n );\n } catch (parseError) {\n pruneContext.hasUnparsableSourceFiles = true;\n logger(\n [\n `Could not parse`,\n formatPath(sourceFilePath),\n `for field-usage analysis.`,\n 'Dictionaries whose usage cannot be confirmed will not be pruned.',\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n }\n })\n );\n\n // Phase 2: Framework-specific analysis for Vue / Svelte SFC bindings\n // that Babel scope analysis cannot resolve (`.value` indirection in\n // Vue, `$` prefix in Svelte).\n if (pruneContext.pendingFrameworkAnalysis.size > 0) {\n const vuePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n const sveltePending = new Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >();\n\n for (const [\n filePath,\n entries,\n ] of pruneContext.pendingFrameworkAnalysis) {\n if (filePath.endsWith('.vue')) {\n vuePending.set(filePath, entries);\n } else if (filePath.endsWith('.svelte')) {\n sveltePending.set(filePath, entries);\n }\n }\n\n /** Merge framework-extracted field usage into pruneContext. */\n const mergeFrameworkResult = (\n dictionaryKey: string,\n fields: Set<string> | undefined\n ): void => {\n if (fields && fields.size > 0) {\n // The Babel rename plugin cannot update source-code property\n // accesses for SFC indirect patterns → suppress field renaming.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n const existing =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (existing === 'all') return;\n\n const merged =\n existing instanceof Set\n ? new Set([...existing, ...fields])\n : new Set(fields);\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n merged\n );\n } else {\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n 'all'\n );\n }\n };\n\n // Vue files\n if (vuePending.size > 0) {\n let extractVueIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const vueCompiler = await import('@intlayer/vue-compiler');\n extractVueIntlayerFieldUsage =\n vueCompiler.extractVueIntlayerFieldUsage;\n } catch {\n // @intlayer/vue-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of vuePending) {\n if (!extractVueIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractVueIntlayerFieldUsage(fileCode, entries);\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n\n // Svelte files\n if (sveltePending.size > 0) {\n let extractSvelteIntlayerFieldUsage:\n | ((\n code: string,\n vars: { variableName: string; dictionaryKey: string }[]\n ) => Map<string, Set<string>>)\n | null = null;\n\n try {\n const svelteCompiler = await import(\n '@intlayer/svelte-compiler'\n );\n extractSvelteIntlayerFieldUsage =\n svelteCompiler.extractSvelteIntlayerFieldUsage;\n } catch {\n // @intlayer/svelte-compiler not installed – fall back to 'all'\n }\n\n for (const [filePath, entries] of sveltePending) {\n if (!extractSvelteIntlayerFieldUsage) {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n let fileCode: string;\n try {\n fileCode = await readFile(filePath, 'utf-8');\n } catch {\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(dictionaryKey, undefined);\n }\n continue;\n }\n\n const result = extractSvelteIntlayerFieldUsage(\n fileCode,\n entries\n );\n for (const { dictionaryKey } of entries) {\n mergeFrameworkResult(\n dictionaryKey,\n result.get(dictionaryKey)\n );\n }\n }\n }\n }\n\n // Phase 3: Warn about untracked bindings (plain variable assignments)\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n\n // Phase 4: Build field-rename map for minification\n // Reads each compiled dictionary JSON to discover the full nested\n // user-defined field structure, then builds a NestedRenameMap that\n // assigns short alphabetic aliases at every level.\n if (minify) {\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n\n // Fetch-mode dictionaries are served from a remote API using\n // original field names – renaming would break the client/server\n // contract.\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch')\n continue;\n\n // SFC indirect access: skip field rename for these dictionaries\n // to avoid a JSON ↔ source mismatch at runtime.\n if (\n pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey)\n )\n continue;\n\n // Read dictionary content (static JSON first, then dynamic per-locale)\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(\n dictionariesDir,\n `${dictionaryKey}.json`\n );\n try {\n const raw = await readFile(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n try {\n const dynamicDir = join(\n dynamicDictionariesDir,\n dictionaryKey\n );\n const localeFiles = await readdir(dynamicDir);\n const firstJsonFile = localeFiles.find((f) =>\n f.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = await readFile(\n join(dynamicDir, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary file not readable – skip rename for this key\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(\n dictionaryContent,\n fieldUsage\n );\n\n // Skip dictionaries whose opaque fields have nested user-defined\n // structure – renaming those sub-keys would silently break child\n // components that consume the field value as-is.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(\n dictionaryKey\n );\n\n if (opaqueFieldMap) {\n const dangerousEntries = [...opaqueFieldMap.entries()].filter(\n ([fieldName]) =>\n (nestedRenameMap.get(fieldName)?.children.size ?? 0) > 0\n );\n\n if (dangerousEntries.length > 0) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be minified.`,\n ...dangerousEntries.flatMap(([fieldName, locations]) => [\n `\\n Reason: field`,\n colorize(`'${fieldName}'`, ANSIColors.BLUE),\n `is passed opaquely to a child component: nested keys cannot be safely renamed.`,\n ...locations.map(\n (loc) => `\\n at ${formatPath(loc)}`\n ),\n `Fix: import useIntlayer directly in the component where it is used.`,\n ]),\n ],\n { level: 'warn' }\n );\n continue;\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n }\n },\n },\n\n // Plugin 2: Babel transform\n {\n name: 'vite-intlayer-babel-transform',\n enforce: 'post', // Run after framework transformations (e.g. Vue SFC)\n apply: (_config, env) => {\n const isBuildCommand = env.command === 'build';\n const isEnabled =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isBuildCommand || !isEnabled) return false;\n\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-optimize-plugin-enabled.lock'\n ),\n () =>\n logger([\n `Build optimization ${colorize('enabled', ANSIColors.GREEN)}`,\n colorize('(import mode:', ANSIColors.GREY_DARK),\n colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),\n colorize(')', ANSIColors.GREY_DARK),\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n\n return true;\n },\n\n transform: async (sourceCode, moduleId) => {\n // Strip query parameters added by Vue/Svelte loaders\n // e.g. \"HelloWorld.vue?vue&type=script&setup=true&lang.ts\" → \"HelloWorld.vue\"\n const sourceFilePath = moduleId.split('?', 1)[0];\n\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return null;\n if (!transformableFilesList.includes(sourceFilePath)) return null;\n\n const isDictionaryEntryFile = [\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n ].includes(sourceFilePath);\n\n const isUsingIntlayer = INTLAYER_USAGE_REGEX.test(sourceCode);\n if (!isUsingIntlayer && !isDictionaryEntryFile) return null;\n\n // Step 1: Field rename (must run before the optimize pass, which\n // replaces useIntlayer → useDictionary and erases the dictionary key)\n let codeToOptimize = sourceCode;\n\n if (pruneContext && isUsingIntlayer) {\n const renamedCode = await renameFieldsInSourceFile(\n sourceFilePath,\n sourceCode,\n pruneContext\n );\n if (renamedCode) {\n codeToOptimize = renamedCode;\n }\n }\n\n // Step 2: Optimize (useIntlayer('key') → useDictionary(_hash))\n const transformResult = await optimizeSourceFile(\n codeToOptimize,\n sourceFilePath,\n {\n optimize,\n dictionariesDir,\n dictionariesEntryPath,\n unmergedDictionariesEntryPath,\n unmergedDictionariesDir,\n dynamicDictionariesDir,\n dynamicDictionariesEntryPath,\n fetchDictionariesDir,\n fetchDictionariesEntryPath: join(\n mainDir,\n 'fetch_dictionaries.mjs'\n ),\n importMode,\n filesList: transformableFilesList,\n replaceDictionaryEntry: true,\n dictionaryModeMap: dictionaryKeyToImportModeMap,\n }\n );\n\n if (!transformResult) return null;\n\n return {\n code: transformResult.code,\n map: transformResult.map as any,\n };\n },\n },\n ];\n } catch (pluginInitError) {\n console.warn(\n '[vite-intlayer] Failed to initialise optimization plugin:',\n pluginInitError\n );\n return [];\n }\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { intlayerMinify } from "./intlayerMinifyPlugin.mjs";
|
|
2
|
+
import { intlayerPrune } from "./intlayerPrunePlugin.mjs";
|
|
1
3
|
import { intlayerOptimize } from "./intlayerOptimizePlugin.mjs";
|
|
2
4
|
import { resolve } from "node:path";
|
|
5
|
+
import { createPruneContext } from "@intlayer/babel";
|
|
3
6
|
import { BLUE } from "@intlayer/config/colors";
|
|
4
7
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
5
8
|
import { getConfiguration } from "@intlayer/config/node";
|
|
@@ -77,7 +80,10 @@ const intlayerPlugin = (configOptions) => {
|
|
|
77
80
|
if (server.config.mode === "development") watch({ configuration: intlayerConfig });
|
|
78
81
|
}
|
|
79
82
|
}];
|
|
80
|
-
|
|
83
|
+
const pruneContext = createPruneContext();
|
|
84
|
+
plugins.push(intlayerOptimize(intlayerConfig, pruneContext));
|
|
85
|
+
plugins.push(intlayerPrune(intlayerConfig, pruneContext));
|
|
86
|
+
plugins.push(intlayerMinify(intlayerConfig, pruneContext));
|
|
81
87
|
return plugins;
|
|
82
88
|
};
|
|
83
89
|
/**
|