vite-intlayer 8.1.2 → 8.1.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.
- package/dist/esm/IntlayerCompilerPlugin.mjs +1 -518
- package/dist/esm/IntlayerCompilerPlugin.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -6
- package/dist/esm/intlayerPlugin.mjs +1 -99
- package/dist/esm/intlayerPlugin.mjs.map +1 -1
- package/dist/esm/intlayerProxyPlugin.mjs +1 -283
- package/dist/esm/intlayerProxyPlugin.mjs.map +1 -1
- package/dist/esm/intlayerPrunePlugin.mjs +1 -103
- package/dist/esm/intlayerPrunePlugin.mjs.map +1 -1
- package/dist/esm/intlayerVueAsyncPlugin.mjs +1 -34
- package/dist/esm/intlayerVueAsyncPlugin.mjs.map +1 -1
- package/dist/types/IntlayerCompilerPlugin.d.ts +1 -1
- package/dist/types/IntlayerCompilerPlugin.d.ts.map +1 -1
- package/dist/types/intlayerPlugin.d.ts +1 -1
- package/dist/types/intlayerProxyPlugin.d.ts +1 -1
- package/dist/types/intlayerProxyPlugin.d.ts.map +1 -1
- package/dist/types/intlayerPrunePlugin.d.ts.map +1 -1
- package/package.json +10 -10
|
@@ -1,519 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readFile } from "node:fs/promises";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { join, relative } from "node:path";
|
|
5
|
-
import { intlayerExtractBabelPlugin } from "@intlayer/babel";
|
|
6
|
-
import { buildDictionary, buildFilesList, prepareIntlayer, writeContentDeclaration } from "@intlayer/chokidar";
|
|
7
|
-
import { ANSIColors, colorize, colorizeKey, colorizePath, getAppLogger, getConfiguration } from "@intlayer/config";
|
|
8
|
-
|
|
9
|
-
//#region src/IntlayerCompilerPlugin.ts
|
|
10
|
-
/**
|
|
11
|
-
* Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer
|
|
12
|
-
*
|
|
13
|
-
* This autonomous compiler handles:
|
|
14
|
-
* - Configuration loading and management
|
|
15
|
-
* - Hot Module Replacement (HMR) for content changes
|
|
16
|
-
* - File transformation with content extraction
|
|
17
|
-
* - Dictionary persistence and building
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* // vite.config.ts
|
|
22
|
-
* import { defineConfig } from 'vite';
|
|
23
|
-
* import { intlayerCompiler } from 'vite-intlayer';
|
|
24
|
-
*
|
|
25
|
-
* export default defineConfig({
|
|
26
|
-
* plugins: [intlayerCompiler()],
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
const intlayerCompiler = (options) => {
|
|
31
|
-
let config;
|
|
32
|
-
let logger;
|
|
33
|
-
let projectRoot = "";
|
|
34
|
-
let filesList = [];
|
|
35
|
-
let babel = null;
|
|
36
|
-
let pendingDictionaryWrite = null;
|
|
37
|
-
const recentlyProcessedFiles = /* @__PURE__ */ new Map();
|
|
38
|
-
const recentDictionaryContent = /* @__PURE__ */ new Map();
|
|
39
|
-
const DEBOUNCE_MS = 500;
|
|
40
|
-
const configOptions = options?.configOptions;
|
|
41
|
-
const customCompilerConfig = options?.compilerConfig;
|
|
42
|
-
/**
|
|
43
|
-
* Check if a file was recently processed (within debounce window)
|
|
44
|
-
* and should be skipped to prevent infinite loops
|
|
45
|
-
*/
|
|
46
|
-
const wasRecentlyProcessed = (filePath) => {
|
|
47
|
-
const lastProcessed = recentlyProcessedFiles.get(filePath);
|
|
48
|
-
if (!lastProcessed) return false;
|
|
49
|
-
return Date.now() - lastProcessed < DEBOUNCE_MS;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Mark a file as recently processed
|
|
53
|
-
*/
|
|
54
|
-
const markAsProcessed = (filePath) => {
|
|
55
|
-
recentlyProcessedFiles.set(filePath, Date.now());
|
|
56
|
-
const now = Date.now();
|
|
57
|
-
for (const [path, timestamp] of recentlyProcessedFiles.entries()) if (now - timestamp > DEBOUNCE_MS * 2) recentlyProcessedFiles.delete(path);
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Create a simple hash of content for comparison
|
|
61
|
-
* Used to detect if dictionary content has actually changed
|
|
62
|
-
*/
|
|
63
|
-
const hashContent = (content) => {
|
|
64
|
-
return JSON.stringify(Object.keys(content).sort().map((k) => [k, content[k]]));
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Check if dictionary content has changed since last write
|
|
68
|
-
*/
|
|
69
|
-
const hasDictionaryContentChanged = (dictionaryKey, content) => {
|
|
70
|
-
const newHash = hashContent(content);
|
|
71
|
-
if (recentDictionaryContent.get(dictionaryKey) === newHash) return false;
|
|
72
|
-
recentDictionaryContent.set(dictionaryKey, newHash);
|
|
73
|
-
return true;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Get compiler config from intlayer config or custom options
|
|
77
|
-
*/
|
|
78
|
-
const getCompilerConfig = () => {
|
|
79
|
-
const rawConfig = config;
|
|
80
|
-
return {
|
|
81
|
-
enabled: customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,
|
|
82
|
-
transformPattern: customCompilerConfig?.transformPattern ?? rawConfig.compiler?.transformPattern ?? config.build.traversePattern,
|
|
83
|
-
excludePattern: [
|
|
84
|
-
...customCompilerConfig?.excludePattern ?? [],
|
|
85
|
-
"**/node_modules/**",
|
|
86
|
-
...config.content.fileExtensions.map((pattern) => `*${pattern}`)
|
|
87
|
-
],
|
|
88
|
-
outputDir: customCompilerConfig?.outputDir ?? rawConfig.compiler?.outputDir ?? "compiler"
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
/**
|
|
92
|
-
* Get the output directory path for compiler dictionaries
|
|
93
|
-
*/
|
|
94
|
-
const getOutputDir = () => {
|
|
95
|
-
const { baseDir } = config.content;
|
|
96
|
-
return join(baseDir, getCompilerConfig().outputDir);
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* Get the file path for a dictionary
|
|
100
|
-
*/
|
|
101
|
-
const getDictionaryFilePath = (dictionaryKey) => {
|
|
102
|
-
return join(getOutputDir(), `${dictionaryKey}.content.json`);
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Read an existing dictionary file if it exists
|
|
106
|
-
*/
|
|
107
|
-
const readExistingDictionary = async (dictionaryKey) => {
|
|
108
|
-
const filePath = getDictionaryFilePath(dictionaryKey);
|
|
109
|
-
if (!existsSync(filePath)) return null;
|
|
110
|
-
try {
|
|
111
|
-
const content = await readFile(filePath, "utf-8");
|
|
112
|
-
return JSON.parse(content);
|
|
113
|
-
} catch {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* Merge extracted content with existing dictionary for multilingual format.
|
|
119
|
-
* - Keys in extracted but not in existing: added with default locale only
|
|
120
|
-
* - Keys in both: preserve existing translations, update default locale value
|
|
121
|
-
* - Keys in existing but not in extracted: removed (no longer in source)
|
|
122
|
-
*/
|
|
123
|
-
const mergeWithExistingMultilingualDictionary = (extractedContent, existingDictionary, defaultLocale) => {
|
|
124
|
-
const mergedContent = {};
|
|
125
|
-
const existingContent = existingDictionary?.content;
|
|
126
|
-
for (const [key, value] of Object.entries(extractedContent)) {
|
|
127
|
-
const existingEntry = existingContent?.[key];
|
|
128
|
-
if (existingEntry && existingEntry.nodeType === "translation" && existingEntry.translation) {
|
|
129
|
-
const oldValue = existingEntry.translation[defaultLocale];
|
|
130
|
-
const isUpdated = oldValue !== value;
|
|
131
|
-
mergedContent[key] = {
|
|
132
|
-
nodeType: "translation",
|
|
133
|
-
translation: {
|
|
134
|
-
...existingEntry.translation,
|
|
135
|
-
[defaultLocale]: value
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
if (isUpdated) logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Updated "${key}" [${defaultLocale}]: "${oldValue?.slice(0, 30)}..." → "${value.slice(0, 30)}..."`, {
|
|
139
|
-
level: "info",
|
|
140
|
-
isVerbose: true
|
|
141
|
-
});
|
|
142
|
-
} else {
|
|
143
|
-
mergedContent[key] = {
|
|
144
|
-
nodeType: "translation",
|
|
145
|
-
translation: { [defaultLocale]: value }
|
|
146
|
-
};
|
|
147
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Added new key "${key}"`, {
|
|
148
|
-
level: "info",
|
|
149
|
-
isVerbose: true
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (existingContent) {
|
|
154
|
-
const removedKeys = Object.keys(existingContent).filter((key) => !(key in extractedContent));
|
|
155
|
-
for (const key of removedKeys) logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Removed key "${key}" (no longer in source)`, {
|
|
156
|
-
level: "info",
|
|
157
|
-
isVerbose: true
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
return mergedContent;
|
|
161
|
-
};
|
|
162
|
-
/**
|
|
163
|
-
* Merge extracted content with existing dictionary for per-locale format.
|
|
164
|
-
* - Keys in extracted but not in existing: added
|
|
165
|
-
* - Keys in both: update value
|
|
166
|
-
* - Keys in existing but not in extracted: removed (no longer in source)
|
|
167
|
-
*/
|
|
168
|
-
const mergeWithExistingPerLocaleDictionary = (extractedContent, existingDictionary, defaultLocale) => {
|
|
169
|
-
const mergedContent = {};
|
|
170
|
-
const existingContent = existingDictionary?.content;
|
|
171
|
-
for (const [key, value] of Object.entries(extractedContent)) {
|
|
172
|
-
const existingValue = existingContent?.[key];
|
|
173
|
-
if (existingValue && typeof existingValue === "string") {
|
|
174
|
-
const isUpdated = existingValue !== value;
|
|
175
|
-
mergedContent[key] = value;
|
|
176
|
-
if (isUpdated) logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Updated "${key}" [${defaultLocale}]: "${existingValue?.slice(0, 30)}..." → "${value.slice(0, 30)}..."`, {
|
|
177
|
-
level: "info",
|
|
178
|
-
isVerbose: true
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
mergedContent[key] = value;
|
|
182
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Added new key "${key}"`, {
|
|
183
|
-
level: "info",
|
|
184
|
-
isVerbose: true
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (existingContent) {
|
|
189
|
-
const removedKeys = Object.keys(existingContent).filter((key) => !(key in extractedContent));
|
|
190
|
-
for (const key of removedKeys) logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Removed key "${key}" (no longer in source)`, {
|
|
191
|
-
level: "info",
|
|
192
|
-
isVerbose: true
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return mergedContent;
|
|
196
|
-
};
|
|
197
|
-
/**
|
|
198
|
-
* Build the list of files to transform based on configuration patterns
|
|
199
|
-
*/
|
|
200
|
-
const buildFilesListFn = async () => {
|
|
201
|
-
const { baseDir, fileExtensions } = config.content;
|
|
202
|
-
const compilerConfig = getCompilerConfig();
|
|
203
|
-
const excludePatterns = Array.isArray(compilerConfig.excludePattern) ? compilerConfig.excludePattern : [compilerConfig.excludePattern];
|
|
204
|
-
filesList = buildFilesList({
|
|
205
|
-
transformPattern: compilerConfig.transformPattern,
|
|
206
|
-
excludePattern: [
|
|
207
|
-
...excludePatterns,
|
|
208
|
-
"**/node_modules/**",
|
|
209
|
-
...fileExtensions.map((pattern) => `**/*${pattern}`)
|
|
210
|
-
],
|
|
211
|
-
baseDir
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
/**
|
|
215
|
-
* Initialize the compiler with the given mode
|
|
216
|
-
*/
|
|
217
|
-
const init = async (_compilerMode) => {
|
|
218
|
-
config = getConfiguration(configOptions);
|
|
219
|
-
logger = getAppLogger(config);
|
|
220
|
-
try {
|
|
221
|
-
babel = createRequire(import.meta.url)("@babel/core");
|
|
222
|
-
} catch {
|
|
223
|
-
logger("Failed to load @babel/core. Transformation will be disabled.", { level: "warn" });
|
|
224
|
-
}
|
|
225
|
-
await buildFilesListFn();
|
|
226
|
-
};
|
|
227
|
-
/**
|
|
228
|
-
* Vite hook: config
|
|
229
|
-
* Called before Vite config is resolved - perfect time to prepare dictionaries
|
|
230
|
-
*/
|
|
231
|
-
const configHook = async (_config, env) => {
|
|
232
|
-
config = getConfiguration(configOptions);
|
|
233
|
-
logger = getAppLogger(config);
|
|
234
|
-
const isDevCommand = env.command === "serve" && env.mode === "development";
|
|
235
|
-
const isBuildCommand = env.command === "build";
|
|
236
|
-
if (isDevCommand || isBuildCommand) await prepareIntlayer(config, {
|
|
237
|
-
clean: isBuildCommand,
|
|
238
|
-
cacheTimeoutMs: isBuildCommand ? 1e3 * 30 : 1e3 * 60 * 60
|
|
239
|
-
});
|
|
240
|
-
};
|
|
241
|
-
/**
|
|
242
|
-
* Vite hook: configResolved
|
|
243
|
-
* Called when Vite config is resolved
|
|
244
|
-
*/
|
|
245
|
-
const configResolved = async (viteConfig) => {
|
|
246
|
-
const compilerMode = viteConfig.env?.DEV ? "dev" : "build";
|
|
247
|
-
projectRoot = viteConfig.root;
|
|
248
|
-
await init(compilerMode);
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* Build start hook - no longer needs to prepare dictionaries
|
|
252
|
-
* The compiler is now autonomous and extracts content inline
|
|
253
|
-
*/
|
|
254
|
-
const buildStart = async () => {
|
|
255
|
-
logger("Intlayer compiler initialized", { level: "info" });
|
|
256
|
-
};
|
|
257
|
-
/**
|
|
258
|
-
* Build end hook - wait for any pending dictionary writes
|
|
259
|
-
*/
|
|
260
|
-
const buildEnd = async () => {
|
|
261
|
-
if (pendingDictionaryWrite) await pendingDictionaryWrite;
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* Configure the dev server
|
|
265
|
-
*/
|
|
266
|
-
const configureServer = async () => {};
|
|
267
|
-
/**
|
|
268
|
-
* Vite hook: handleHotUpdate
|
|
269
|
-
* Handles HMR for content files - invalidates cache and triggers re-transform
|
|
270
|
-
*/
|
|
271
|
-
const handleHotUpdate = async (ctx) => {
|
|
272
|
-
const { file, server, modules } = ctx;
|
|
273
|
-
if (filesList.some((f) => f === file)) {
|
|
274
|
-
if (wasRecentlyProcessed(file)) {
|
|
275
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`, {
|
|
276
|
-
level: "info",
|
|
277
|
-
isVerbose: true
|
|
278
|
-
});
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
markAsProcessed(file);
|
|
282
|
-
for (const mod of modules) server.moduleGraph.invalidateModule(mod);
|
|
283
|
-
try {
|
|
284
|
-
await transformHandler(await readFile(file, "utf-8"), file);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`, { level: "error" });
|
|
287
|
-
}
|
|
288
|
-
server.ws.send({ type: "full-reload" });
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
/**
|
|
293
|
-
* Write and build a single dictionary immediately
|
|
294
|
-
* This is called during transform to ensure dictionaries are always up-to-date.
|
|
295
|
-
*
|
|
296
|
-
* The merge strategy:
|
|
297
|
-
* - New keys are added with the default locale only
|
|
298
|
-
* - Existing keys preserve their translations, with default locale updated
|
|
299
|
-
* - Keys no longer in source are removed
|
|
300
|
-
*
|
|
301
|
-
* Dictionary format:
|
|
302
|
-
* - Per-locale: When config.dictionary.locale is set, content is simple strings with locale property
|
|
303
|
-
* - Multilingual: When not set, content is wrapped in translation nodes without locale property
|
|
304
|
-
*/
|
|
305
|
-
const writeAndBuildDictionary = async (result) => {
|
|
306
|
-
const { dictionaryKey, content } = result;
|
|
307
|
-
if (!hasDictionaryContentChanged(dictionaryKey, content)) {
|
|
308
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`, {
|
|
309
|
-
level: "info",
|
|
310
|
-
isVerbose: true
|
|
311
|
-
});
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const outputDir = getOutputDir();
|
|
315
|
-
const { defaultLocale } = config.internationalization;
|
|
316
|
-
const isPerLocaleFile = Boolean(config?.dictionary?.locale);
|
|
317
|
-
await mkdir(outputDir, { recursive: true });
|
|
318
|
-
const existingDictionary = await readExistingDictionary(dictionaryKey);
|
|
319
|
-
const relativeFilePath = join(relative(config.content.baseDir, outputDir), `${dictionaryKey}.content.json`);
|
|
320
|
-
let mergedDictionary;
|
|
321
|
-
if (isPerLocaleFile) {
|
|
322
|
-
const mergedContent = mergeWithExistingPerLocaleDictionary(content, existingDictionary, defaultLocale);
|
|
323
|
-
mergedDictionary = {
|
|
324
|
-
...existingDictionary && {
|
|
325
|
-
$schema: existingDictionary.$schema,
|
|
326
|
-
id: existingDictionary.id,
|
|
327
|
-
title: existingDictionary.title,
|
|
328
|
-
description: existingDictionary.description,
|
|
329
|
-
tags: existingDictionary.tags,
|
|
330
|
-
fill: existingDictionary.fill,
|
|
331
|
-
filled: existingDictionary.filled,
|
|
332
|
-
priority: existingDictionary.priority,
|
|
333
|
-
version: existingDictionary.version
|
|
334
|
-
},
|
|
335
|
-
key: dictionaryKey,
|
|
336
|
-
content: mergedContent,
|
|
337
|
-
locale: defaultLocale,
|
|
338
|
-
filePath: relativeFilePath
|
|
339
|
-
};
|
|
340
|
-
} else {
|
|
341
|
-
const mergedContent = mergeWithExistingMultilingualDictionary(content, existingDictionary, defaultLocale);
|
|
342
|
-
mergedDictionary = {
|
|
343
|
-
...existingDictionary && {
|
|
344
|
-
$schema: existingDictionary.$schema,
|
|
345
|
-
id: existingDictionary.id,
|
|
346
|
-
title: existingDictionary.title,
|
|
347
|
-
description: existingDictionary.description,
|
|
348
|
-
tags: existingDictionary.tags,
|
|
349
|
-
fill: existingDictionary.fill,
|
|
350
|
-
filled: existingDictionary.filled,
|
|
351
|
-
priority: existingDictionary.priority,
|
|
352
|
-
version: existingDictionary.version
|
|
353
|
-
},
|
|
354
|
-
key: dictionaryKey,
|
|
355
|
-
content: mergedContent,
|
|
356
|
-
filePath: relativeFilePath
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
const writeResult = await writeContentDeclaration(mergedDictionary, config, { newDictionariesPath: relative(config.content.baseDir, outputDir) });
|
|
361
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} ${writeResult.status === "created" ? "Created" : writeResult.status === "updated" ? "Updated" : "Processed"} content declaration: ${colorizePath(relative(projectRoot, writeResult.path))}`, { level: "info" });
|
|
362
|
-
const dictionaryToBuild = {
|
|
363
|
-
...mergedDictionary,
|
|
364
|
-
filePath: relative(config.content.baseDir, writeResult.path)
|
|
365
|
-
};
|
|
366
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Building dictionary ${colorizeKey(dictionaryKey)}`, { level: "info" });
|
|
367
|
-
await buildDictionary([dictionaryToBuild], config);
|
|
368
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Dictionary ${colorizeKey(dictionaryKey)} built successfully`, { level: "info" });
|
|
369
|
-
} catch (error) {
|
|
370
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`, { level: "error" });
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
/**
|
|
374
|
-
* Callback for when content is extracted from a file
|
|
375
|
-
* Immediately writes and builds the dictionary
|
|
376
|
-
*/
|
|
377
|
-
const handleExtractedContent = (result) => {
|
|
378
|
-
const contentKeys = Object.keys(result.content);
|
|
379
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Extracted ${contentKeys.length} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`, { level: "info" });
|
|
380
|
-
pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve()).then(() => writeAndBuildDictionary(result)).catch((error) => {
|
|
381
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`, { level: "error" });
|
|
382
|
-
});
|
|
383
|
-
};
|
|
384
|
-
/**
|
|
385
|
-
* Detect the package name to import useIntlayer from based on file extension
|
|
386
|
-
*/
|
|
387
|
-
const detectPackageName = (filename) => {
|
|
388
|
-
if (filename.endsWith(".vue")) return "vue-intlayer";
|
|
389
|
-
if (filename.endsWith(".svelte")) return "svelte-intlayer";
|
|
390
|
-
if (filename.endsWith(".tsx") || filename.endsWith(".jsx")) return "react-intlayer";
|
|
391
|
-
return "intlayer";
|
|
392
|
-
};
|
|
393
|
-
/**
|
|
394
|
-
* Transform a Vue file using the Vue extraction plugin
|
|
395
|
-
*/
|
|
396
|
-
const transformVue = async (code, filename, defaultLocale) => {
|
|
397
|
-
const { intlayerVueExtract } = await import("@intlayer/vue-compiler");
|
|
398
|
-
return intlayerVueExtract(code, filename, {
|
|
399
|
-
defaultLocale,
|
|
400
|
-
filesList,
|
|
401
|
-
packageName: "vue-intlayer",
|
|
402
|
-
onExtract: handleExtractedContent
|
|
403
|
-
});
|
|
404
|
-
};
|
|
405
|
-
/**
|
|
406
|
-
* Transform a Svelte file using the Svelte extraction plugin
|
|
407
|
-
*/
|
|
408
|
-
const transformSvelte = async (code, filename, defaultLocale) => {
|
|
409
|
-
const { intlayerSvelteExtract } = await import("@intlayer/svelte-compiler");
|
|
410
|
-
return await intlayerSvelteExtract(code, filename, {
|
|
411
|
-
defaultLocale,
|
|
412
|
-
filesList,
|
|
413
|
-
packageName: "svelte-intlayer",
|
|
414
|
-
onExtract: handleExtractedContent
|
|
415
|
-
});
|
|
416
|
-
};
|
|
417
|
-
/**
|
|
418
|
-
* Transform a JSX/TSX file using the Babel extraction plugin
|
|
419
|
-
*/
|
|
420
|
-
const transformJsx = (code, filename, defaultLocale) => {
|
|
421
|
-
if (!babel) return;
|
|
422
|
-
const packageName = detectPackageName(filename);
|
|
423
|
-
const result = babel.transformSync(code, {
|
|
424
|
-
filename,
|
|
425
|
-
plugins: [[intlayerExtractBabelPlugin, {
|
|
426
|
-
defaultLocale,
|
|
427
|
-
filesList,
|
|
428
|
-
packageName,
|
|
429
|
-
onExtract: handleExtractedContent
|
|
430
|
-
}]],
|
|
431
|
-
parserOpts: {
|
|
432
|
-
sourceType: "module",
|
|
433
|
-
allowImportExportEverywhere: true,
|
|
434
|
-
plugins: [
|
|
435
|
-
"typescript",
|
|
436
|
-
"jsx",
|
|
437
|
-
"decorators-legacy",
|
|
438
|
-
"classProperties",
|
|
439
|
-
"objectRestSpread",
|
|
440
|
-
"asyncGenerators",
|
|
441
|
-
"functionBind",
|
|
442
|
-
"exportDefaultFrom",
|
|
443
|
-
"exportNamespaceFrom",
|
|
444
|
-
"dynamicImport",
|
|
445
|
-
"nullishCoalescingOperator",
|
|
446
|
-
"optionalChaining"
|
|
447
|
-
]
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
if (result?.code) return {
|
|
451
|
-
code: result.code,
|
|
452
|
-
map: result.map,
|
|
453
|
-
extracted: true
|
|
454
|
-
};
|
|
455
|
-
};
|
|
456
|
-
/**
|
|
457
|
-
* Transform a file using the appropriate extraction plugin based on file type
|
|
458
|
-
*/
|
|
459
|
-
const transformHandler = async (code, id, _options) => {
|
|
460
|
-
if (!getCompilerConfig().enabled) return;
|
|
461
|
-
if (id.includes("?")) return;
|
|
462
|
-
const { defaultLocale } = config.internationalization;
|
|
463
|
-
const filename = id;
|
|
464
|
-
if (!filesList.includes(filename)) return;
|
|
465
|
-
const isVue = filename.endsWith(".vue");
|
|
466
|
-
const isSvelte = filename.endsWith(".svelte");
|
|
467
|
-
if (!isVue && !isSvelte) {
|
|
468
|
-
try {
|
|
469
|
-
const result = transformJsx(code, filename, defaultLocale);
|
|
470
|
-
if (pendingDictionaryWrite) await pendingDictionaryWrite;
|
|
471
|
-
if (result?.code) return {
|
|
472
|
-
code: result.code,
|
|
473
|
-
map: result.map
|
|
474
|
-
};
|
|
475
|
-
} catch (error) {
|
|
476
|
-
logger(`Failed to transform ${colorizePath(relative(projectRoot, filename))}: ${error}`, { level: "error" });
|
|
477
|
-
}
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`, { level: "info" });
|
|
481
|
-
try {
|
|
482
|
-
let result;
|
|
483
|
-
if (isVue) result = await transformVue(code, filename, defaultLocale);
|
|
484
|
-
else if (isSvelte) result = await transformSvelte(code, filename, defaultLocale);
|
|
485
|
-
if (pendingDictionaryWrite) await pendingDictionaryWrite;
|
|
486
|
-
if (result?.code) return {
|
|
487
|
-
code: result.code,
|
|
488
|
-
map: result.map
|
|
489
|
-
};
|
|
490
|
-
} catch (error) {
|
|
491
|
-
logger(`Failed to transform ${relative(projectRoot, filename)}: ${error}`, { level: "error" });
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
/**
|
|
495
|
-
* Apply hook for determining when plugin should be active
|
|
496
|
-
*/
|
|
497
|
-
const apply = (_config, _env) => {
|
|
498
|
-
return getCompilerConfig().enabled;
|
|
499
|
-
};
|
|
500
|
-
return {
|
|
501
|
-
name: "vite-intlayer-compiler",
|
|
502
|
-
enforce: "pre",
|
|
503
|
-
config: configHook,
|
|
504
|
-
configResolved,
|
|
505
|
-
buildStart,
|
|
506
|
-
buildEnd,
|
|
507
|
-
configureServer,
|
|
508
|
-
handleHotUpdate,
|
|
509
|
-
transform: transformHandler,
|
|
510
|
-
apply: (_viteConfig, env) => {
|
|
511
|
-
if (!config) config = getConfiguration(configOptions);
|
|
512
|
-
return apply(_viteConfig, env);
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
//#endregion
|
|
518
|
-
export { intlayerCompiler };
|
|
1
|
+
import{existsSync as e}from"node:fs";import{mkdir as t,readFile as n}from"node:fs/promises";import{createRequire as r}from"node:module";import{join as i,relative as a}from"node:path";import{intlayerExtractBabelPlugin as o}from"@intlayer/babel";import{buildDictionary as s,prepareIntlayer as c,writeContentDeclaration as l}from"@intlayer/chokidar/build";import{buildFilesList as u}from"@intlayer/chokidar/utils";import{ANSIColors as d,colorize as f,colorizeKey as p,colorizePath as m,getAppLogger as h}from"@intlayer/config/logger";import{getConfiguration as g}from"@intlayer/config/node";const _=_=>{let v,y,b=``,x=[],S=null,C=null,w=new Map,T=new Map,E=_?.configOptions,D=_?.compilerConfig,O=e=>{let t=w.get(e);return t?Date.now()-t<500:!1},k=e=>{w.set(e,Date.now());let t=Date.now();for(let[e,n]of w.entries())t-n>500*2&&w.delete(e)},A=e=>JSON.stringify(Object.keys(e).sort().map(t=>[t,e[t]])),j=(e,t)=>{let n=A(t);return T.get(e)===n?!1:(T.set(e,n),!0)},M=()=>{let e=v;return{enabled:D?.enabled??e.compiler?.enabled??!0,transformPattern:D?.transformPattern??e.compiler?.transformPattern??v.build.traversePattern,excludePattern:[...D?.excludePattern??[],`**/node_modules/**`,...v.content.fileExtensions.map(e=>`*${e}`)],outputDir:D?.outputDir??e.compiler?.outputDir??`compiler`}},N=()=>{let{baseDir:e}=v.content;return i(e,M().outputDir)},P=e=>i(N(),`${e}.content.json`),F=async t=>{let r=P(t);if(!e(r))return null;try{let e=await n(r,`utf-8`);return JSON.parse(e)}catch{return null}},I=(e,t,n)=>{let r={},i=t?.content;for(let[t,a]of Object.entries(e)){let e=i?.[t];if(e&&e.nodeType===`translation`&&e.translation){let i=e.translation[n],o=i!==a;r[t]={nodeType:`translation`,translation:{...e.translation,[n]:a}},o&&y(`${f(`Compiler:`,d.GREY_DARK)} Updated "${t}" [${n}]: "${i?.slice(0,30)}..." → "${a.slice(0,30)}..."`,{level:`info`,isVerbose:!0})}else r[t]={nodeType:`translation`,translation:{[n]:a}},y(`${f(`Compiler:`,d.GREY_DARK)} Added new key "${t}"`,{level:`info`,isVerbose:!0})}if(i){let t=Object.keys(i).filter(t=>!(t in e));for(let e of t)y(`${f(`Compiler:`,d.GREY_DARK)} Removed key "${e}" (no longer in source)`,{level:`info`,isVerbose:!0})}return r},L=(e,t,n)=>{let r={},i=t?.content;for(let[t,a]of Object.entries(e)){let e=i?.[t];if(e&&typeof e==`string`){let i=e!==a;r[t]=a,i&&y(`${f(`Compiler:`,d.GREY_DARK)} Updated "${t}" [${n}]: "${e?.slice(0,30)}..." → "${a.slice(0,30)}..."`,{level:`info`,isVerbose:!0})}else r[t]=a,y(`${f(`Compiler:`,d.GREY_DARK)} Added new key "${t}"`,{level:`info`,isVerbose:!0})}if(i){let t=Object.keys(i).filter(t=>!(t in e));for(let e of t)y(`${f(`Compiler:`,d.GREY_DARK)} Removed key "${e}" (no longer in source)`,{level:`info`,isVerbose:!0})}return r},R=async()=>{let{baseDir:e,fileExtensions:t}=v.content,n=M(),r=Array.isArray(n.excludePattern)?n.excludePattern:[n.excludePattern];x=u({transformPattern:n.transformPattern,excludePattern:[...r,`**/node_modules/**`,...t.map(e=>`**/*${e}`)],baseDir:e})},z=async e=>{v=g(E),y=h(v);try{S=r(import.meta.url)(`@babel/core`)}catch{y(`Failed to load @babel/core. Transformation will be disabled.`,{level:`warn`})}await R()},B=async(e,t)=>{v=g(E),y=h(v);let n=t.command===`serve`&&t.mode===`development`,r=t.command===`build`;(n||r)&&await c(v,{clean:r,cacheTimeoutMs:r?1e3*30:1e3*60*60})},V=async e=>{let t=e.env?.DEV?`dev`:`build`;b=e.root,await z(t)},H=async()=>{y(`Intlayer compiler initialized`,{level:`info`})},U=async()=>{C&&await C},W=async()=>{},G=async e=>{let{file:t,server:r,modules:i}=e;if(x.some(e=>e===t)){if(O(t)){y(`${f(`Compiler:`,d.GREY_DARK)} Skipping re-transform of ${m(a(b,t))} (recently processed)`,{level:`info`,isVerbose:!0});return}k(t);for(let e of i)r.moduleGraph.invalidateModule(e);try{await Q(await n(t,`utf-8`),t)}catch(e){y(`${f(`Compiler:`,d.GREY_DARK)} Failed to re-transform ${t}: ${e}`,{level:`error`})}return r.ws.send({type:`full-reload`}),[]}},K=async e=>{let{dictionaryKey:n,content:r}=e;if(!j(n,r)){y(`${f(`Compiler:`,d.GREY_DARK)} Skipping dictionary ${p(n)} (content unchanged)`,{level:`info`,isVerbose:!0});return}let o=N(),{defaultLocale:c}=v.internationalization,u=!!v?.dictionary?.locale;await t(o,{recursive:!0});let h=await F(n),g=i(a(v.content.baseDir,o),`${n}.content.json`),_;if(u){let e=L(r,h,c);_={...h&&{$schema:h.$schema,id:h.id,title:h.title,description:h.description,tags:h.tags,fill:h.fill,filled:h.filled,priority:h.priority,version:h.version},key:n,content:e,locale:c,filePath:g}}else{let e=I(r,h,c);_={...h&&{$schema:h.$schema,id:h.id,title:h.title,description:h.description,tags:h.tags,fill:h.fill,filled:h.filled,priority:h.priority,version:h.version},key:n,content:e,filePath:g}}try{let e=await l(_,v,{newDictionariesPath:a(v.content.baseDir,o)});y(`${f(`Compiler:`,d.GREY_DARK)} ${e.status===`created`?`Created`:e.status===`updated`?`Updated`:`Processed`} content declaration: ${m(a(b,e.path))}`,{level:`info`});let t={..._,filePath:a(v.content.baseDir,e.path)};y(`${f(`Compiler:`,d.GREY_DARK)} Building dictionary ${p(n)}`,{level:`info`}),await s([t],v),y(`${f(`Compiler:`,d.GREY_DARK)} Dictionary ${p(n)} built successfully`,{level:`info`})}catch(e){y(`${f(`Compiler:`,d.GREY_DARK)} Failed to write/build dictionary for ${p(n)}: ${e}`,{level:`error`})}},q=e=>{let t=Object.keys(e.content);y(`${f(`Compiler:`,d.GREY_DARK)} Extracted ${t.length} content keys from ${m(a(b,e.filePath))}`,{level:`info`}),C=(C??Promise.resolve()).then(()=>K(e)).catch(e=>{y(`${f(`Compiler:`,d.GREY_DARK)} Error in dictionary write chain: ${e}`,{level:`error`})})},J=e=>e.endsWith(`.vue`)?`vue-intlayer`:e.endsWith(`.svelte`)?`svelte-intlayer`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)?`react-intlayer`:`intlayer`,Y=async(e,t,n)=>{let{intlayerVueExtract:r}=await import(`@intlayer/vue-compiler`);return r(e,t,{defaultLocale:n,filesList:x,packageName:`vue-intlayer`,onExtract:q})},X=async(e,t,n)=>{let{intlayerSvelteExtract:r}=await import(`@intlayer/svelte-compiler`);return await r(e,t,{defaultLocale:n,filesList:x,packageName:`svelte-intlayer`,onExtract:q})},Z=(e,t,n)=>{if(!S)return;let r=J(t),i=S.transformSync(e,{filename:t,plugins:[[o,{defaultLocale:n,filesList:x,packageName:r,onExtract:q}]],parserOpts:{sourceType:`module`,allowImportExportEverywhere:!0,plugins:[`typescript`,`jsx`,`decorators-legacy`,`classProperties`,`objectRestSpread`,`asyncGenerators`,`functionBind`,`exportDefaultFrom`,`exportNamespaceFrom`,`dynamicImport`,`nullishCoalescingOperator`,`optionalChaining`]}});if(i?.code)return{code:i.code,map:i.map,extracted:!0}},Q=async(e,t,n)=>{if(!M().enabled||t.includes(`?`))return;let{defaultLocale:r}=v.internationalization,i=t;if(!x.includes(i))return;let o=i.endsWith(`.vue`),s=i.endsWith(`.svelte`);if(!o&&!s){try{let t=Z(e,i,r);if(C&&await C,t?.code)return{code:t.code,map:t.map}}catch(e){y(`Failed to transform ${m(a(b,i))}: ${e}`,{level:`error`})}return}y(`${f(`Compiler:`,d.GREY_DARK)} Transforming ${m(a(b,i))}`,{level:`info`});try{let t;if(o?t=await Y(e,i,r):s&&(t=await X(e,i,r)),C&&await C,t?.code)return{code:t.code,map:t.map}}catch(e){y(`Failed to transform ${a(b,i)}: ${e}`,{level:`error`})}},$=(e,t)=>M().enabled;return{name:`vite-intlayer-compiler`,enforce:`pre`,config:B,configResolved:V,buildStart:H,buildEnd:U,configureServer:W,handleHotUpdate:G,transform:Q,apply:(e,t)=>(v||=g(E),$(e,t))}};export{_ as intlayerCompiler};
|
|
519
2
|
//# sourceMappingURL=IntlayerCompilerPlugin.mjs.map
|