@inlang/paraglide-js 2.24.1 → 2.25.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/bundler-plugins/esbuild.d.ts +1 -1
- package/dist/bundler-plugins/per-locale-build/index.d.ts +3 -3
- package/dist/bundler-plugins/per-locale-build/index.js +59 -15
- package/dist/bundler-plugins/per-locale-build/index.js.map +1 -1
- package/dist/bundler-plugins/rolldown.d.ts +1 -1
- package/dist/bundler-plugins/rollup.d.ts +1 -1
- package/dist/bundler-plugins/rspack.d.ts +1 -1
- package/dist/bundler-plugins/unplugin.d.ts +35 -1
- package/dist/bundler-plugins/unplugin.js +404 -123
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +357 -202
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/bundler-plugins/vite.d.ts +2 -2
- package/dist/bundler-plugins/vite.js.map +1 -1
- package/dist/bundler-plugins/vite.test.js +2 -1
- package/dist/bundler-plugins/vite.test.js.map +1 -1
- package/dist/bundler-plugins/webpack.d.ts +1 -1
- package/dist/cli/commands/compile/command.js +156 -36
- package/dist/cli/commands/compile/command.js.map +1 -1
- package/dist/cli/commands/compile/command.test.js +311 -7
- package/dist/cli/commands/compile/command.test.js.map +1 -1
- package/dist/cli/defaults.d.ts +0 -2
- package/dist/cli/defaults.js +0 -2
- package/dist/cli/defaults.js.map +1 -1
- package/dist/cli/steps/initialize-inlang-project.js +2 -1
- package/dist/cli/steps/initialize-inlang-project.js.map +1 -1
- package/dist/cli/steps/prompt-for-outdir.js +1 -1
- package/dist/cli/steps/prompt-for-outdir.js.map +1 -1
- package/dist/compiler/compile-annotation.d.ts +2 -2
- package/dist/compiler/compile-annotation.js +9 -9
- package/dist/compiler/compile-annotation.js.map +1 -1
- package/dist/compiler/compile-local-variable.d.ts +2 -1
- package/dist/compiler/compile-local-variable.js +4 -4
- package/dist/compiler/compile-local-variable.js.map +1 -1
- package/dist/compiler/compile-local-variable.test.js +82 -0
- package/dist/compiler/compile-local-variable.test.js.map +1 -1
- package/dist/compiler/compile-message.js +10 -2
- package/dist/compiler/compile-message.js.map +1 -1
- package/dist/compiler/compile-message.test.js +132 -2
- package/dist/compiler/compile-message.test.js.map +1 -1
- package/dist/compiler/compile-pattern.js +4 -2
- package/dist/compiler/compile-pattern.js.map +1 -1
- package/dist/compiler/compile-pattern.test.js +31 -0
- package/dist/compiler/compile-pattern.test.js.map +1 -1
- package/dist/compiler/compiler-options.d.ts +1 -1
- package/dist/compiler/compiler-options.js.map +1 -1
- package/dist/compiler/server/create-server-file.js +18 -4
- package/dist/compiler/server/create-server-file.js.map +1 -1
- package/dist/compiler/server/create-server-file.test.js +76 -0
- package/dist/compiler/server/create-server-file.test.js.map +1 -1
- package/dist/compiler/variable-access.d.ts +2 -0
- package/dist/compiler/variable-access.js +6 -0
- package/dist/compiler/variable-access.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/config/config-schema.d.ts +69 -0
- package/dist/services/config/config-schema.js +78 -0
- package/dist/services/config/config-schema.js.map +1 -0
- package/dist/services/config/defaults.d.ts +11 -0
- package/dist/services/config/defaults.js +12 -0
- package/dist/services/config/defaults.js.map +1 -0
- package/dist/services/config/discover-config-file.d.ts +19 -0
- package/dist/services/config/discover-config-file.js +39 -0
- package/dist/services/config/discover-config-file.js.map +1 -0
- package/dist/services/config/index.d.ts +24 -0
- package/dist/services/config/index.js +25 -0
- package/dist/services/config/index.js.map +1 -0
- package/dist/services/config/load-paraglide-config.d.ts +44 -0
- package/dist/services/config/load-paraglide-config.js +109 -0
- package/dist/services/config/load-paraglide-config.js.map +1 -0
- package/dist/services/config/load-paraglide-config.test.d.ts +1 -0
- package/dist/services/config/load-paraglide-config.test.js +201 -0
- package/dist/services/config/load-paraglide-config.test.js.map +1 -0
- package/dist/services/config/resolve-compiler-options.d.ts +24 -0
- package/dist/services/config/resolve-compiler-options.js +51 -0
- package/dist/services/config/resolve-compiler-options.js.map +1 -0
- package/dist/services/config/resolve-compiler-options.test.d.ts +1 -0
- package/dist/services/config/resolve-compiler-options.test.js +60 -0
- package/dist/services/config/resolve-compiler-options.test.js.map +1 -0
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/env-variables/index.js.map +1 -1
- package/dist/services/logger/index.js +2 -0
- package/dist/services/logger/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { compile } from "../compiler/compile.js";
|
|
2
|
-
import { dirname, relative, resolve } from "node:path";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import nodeFs from "node:fs";
|
|
5
5
|
import { Logger } from "../services/logger/index.js";
|
|
@@ -7,6 +7,7 @@ import { ENV_VARIABLES } from "../services/env-variables/index.js";
|
|
|
7
7
|
import { createTrackedFs, getWatchTargets, isPathWithinDirectories, } from "../services/file-watching/tracked-fs.js";
|
|
8
8
|
import { nodeNormalizePath } from "../utilities/node-normalize-path.js";
|
|
9
9
|
import { seedPreviousCompilationFromOutdir } from "../compiler/seed-previous-compilation.js";
|
|
10
|
+
import { CONFIG_FILE_NAMES, clearParaglideConfigCache, isConfigFileName, loadParaglideConfig, resolveCompilerOptions, resolveConfigCandidate, } from "../services/config/index.js";
|
|
10
11
|
const PLUGIN_NAME = "unplugin-paraglide-js";
|
|
11
12
|
const logger = new Logger();
|
|
12
13
|
const PERSISTENT_CACHE_VERSION = 1;
|
|
@@ -35,6 +36,8 @@ function getPluginState(args) {
|
|
|
35
36
|
clearReadFiles: tracked.clearReadFiles,
|
|
36
37
|
previousCompilation: undefined,
|
|
37
38
|
previousInputsDigest: undefined,
|
|
39
|
+
previousProject: undefined,
|
|
40
|
+
previousOutdir: undefined,
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
return pluginState;
|
|
@@ -222,130 +225,310 @@ function rethrowUnlessEnoent(error) {
|
|
|
222
225
|
}
|
|
223
226
|
throw error;
|
|
224
227
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
// default to locale-modules for development to speed up the dev server
|
|
229
|
+
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
230
|
+
function resolveOutputStructure(options) {
|
|
231
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
232
|
+
return (options.outputStructure ??
|
|
233
|
+
(isProduction ? "message-modules" : "locale-modules"));
|
|
234
|
+
}
|
|
235
|
+
function hasUnchangedInputs(args) {
|
|
236
|
+
if (args.state.previousCompilation === undefined) {
|
|
237
|
+
return Promise.resolve(false);
|
|
238
|
+
}
|
|
239
|
+
if (args.state.previousInputsDigest === undefined) {
|
|
240
|
+
return Promise.resolve(false);
|
|
241
|
+
}
|
|
242
|
+
return computeInputsDigest(args.state, args.compilerOptions, args.outputStructure).then((currentDigest) => currentDigest === args.state.previousInputsDigest);
|
|
243
|
+
}
|
|
244
|
+
function maybeInvalidateForOptionsChange(state, compilerOptions) {
|
|
245
|
+
if (state.previousProject !== undefined &&
|
|
246
|
+
(state.previousProject !== compilerOptions.project ||
|
|
247
|
+
state.previousOutdir !== compilerOptions.outdir)) {
|
|
248
|
+
state.previousCompilation = undefined;
|
|
249
|
+
state.previousInputsDigest = undefined;
|
|
250
|
+
state.clearReadFiles();
|
|
251
|
+
}
|
|
252
|
+
state.previousProject = compilerOptions.project;
|
|
253
|
+
state.previousOutdir = compilerOptions.outdir;
|
|
254
|
+
}
|
|
255
|
+
// Compiles and stores the result plus its inputs digest on the plugin
|
|
256
|
+
// state. Callers decide whether to persist the compilation cache.
|
|
257
|
+
async function compileAndUpdateState(args) {
|
|
258
|
+
args.state.previousCompilation = await compile({
|
|
259
|
+
previousCompilation: args.previousCompilation,
|
|
260
|
+
outputStructure: args.outputStructure,
|
|
261
|
+
isServer: args.isServer,
|
|
262
|
+
...withoutCleanOutdir(args.compilerOptions),
|
|
263
|
+
cleanOutdir: false,
|
|
264
|
+
// after the options spread so a user-provided fs doesn't bypass
|
|
265
|
+
// the read tracking (trackedFs wraps options.fs when provided)
|
|
266
|
+
fs: args.state.trackedFs,
|
|
267
|
+
});
|
|
268
|
+
args.state.previousInputsDigest = await computeInputsDigest(args.state, args.compilerOptions, args.outputStructure);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Runs the compilation with the given, already-resolved options. Shared by
|
|
272
|
+
* buildStart and watch-mode rebuilds.
|
|
273
|
+
*/
|
|
274
|
+
async function runCompilation(args) {
|
|
275
|
+
const compilerOptions = args.compilerOptions;
|
|
276
|
+
const { state } = args;
|
|
277
|
+
const outputStructure = resolveOutputStructure(compilerOptions);
|
|
278
|
+
maybeInvalidateForOptionsChange(state, compilerOptions);
|
|
279
|
+
try {
|
|
280
|
+
// Stabilize project directory listings before the first digest. The
|
|
281
|
+
// ignored cache directory must not invalidate the cache that creates it.
|
|
282
|
+
await preparePersistentCacheDirectory(compilerOptions, outputStructure);
|
|
283
|
+
if (state.previousCompilation === undefined &&
|
|
284
|
+
(await restorePersistentCache({
|
|
285
|
+
state,
|
|
286
|
+
compilerOptions,
|
|
287
|
+
outputStructure,
|
|
288
|
+
}))) {
|
|
289
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (await hasUnchangedInputs({
|
|
293
|
+
state,
|
|
294
|
+
compilerOptions,
|
|
295
|
+
outputStructure,
|
|
296
|
+
})) {
|
|
297
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// On a fresh process, seed previousCompilation from on-disk hashes
|
|
301
|
+
// so the first compile is a no-op when inputs are unchanged. Avoids
|
|
302
|
+
// racing concurrent readers that wiping outdir would interrupt.
|
|
303
|
+
const seededPrevious = state.previousCompilation ??
|
|
304
|
+
(await seedPreviousCompilationFromOutdir({
|
|
305
|
+
outdir: compilerOptions.outdir,
|
|
306
|
+
fs: compilerOptions.fs?.promises,
|
|
307
|
+
}));
|
|
308
|
+
await compileAndUpdateState({
|
|
309
|
+
state,
|
|
310
|
+
compilerOptions,
|
|
311
|
+
outputStructure,
|
|
312
|
+
previousCompilation: seededPrevious,
|
|
313
|
+
isServer,
|
|
314
|
+
});
|
|
315
|
+
await writePersistentCache(state, compilerOptions, outputStructure);
|
|
316
|
+
logger.success(`Compilation complete (${outputStructure})`);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
state.previousInputsDigest = undefined;
|
|
320
|
+
logger.error("Failed to compile project:", error.message);
|
|
321
|
+
logger.info("Please check your translation files for syntax errors.");
|
|
322
|
+
if (process.env.NODE_ENV === "production")
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export const unpluginFactory = (userArgs) => {
|
|
327
|
+
const state = getPluginState(userArgs);
|
|
328
|
+
const { readFiles, clearReadFiles } = state;
|
|
329
|
+
// Project root as reported by the bundler (vite `root`, webpack/rspack
|
|
330
|
+
// `context`). Falls back to the process working directory when the
|
|
331
|
+
// bundler doesn't provide one. The config file is resolved inside the
|
|
332
|
+
// project directory.
|
|
333
|
+
let discoveryRoot;
|
|
334
|
+
// Bound to the hook context in buildStart so that watchChange can
|
|
335
|
+
// register additional files.
|
|
336
|
+
let addWatchFile;
|
|
337
|
+
// A new plugin instance must not be served by config cache entries
|
|
338
|
+
// from a previous instance (e.g. vite.config.ts reloads reuse the
|
|
339
|
+
// process). Wiping the cache here makes every instance perform one
|
|
340
|
+
// fresh load; subsequent loads within the instance stay cached.
|
|
341
|
+
clearParaglideConfigCache();
|
|
342
|
+
function safeAddWatchFile(thisArg, path) {
|
|
343
|
+
const fn = thisArg?.addWatchFile ?? addWatchFile;
|
|
344
|
+
if (!fn)
|
|
345
|
+
return;
|
|
346
|
+
if (watchRegistrationUnsupported)
|
|
347
|
+
return;
|
|
348
|
+
try {
|
|
349
|
+
fn.call(thisArg ?? undefined, path);
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
// Watch-file registration is best-effort: backends differ in what
|
|
353
|
+
// they support (unplugin's esbuild context throws for every call),
|
|
354
|
+
// and watcher resource limits can surface as errors. A failed
|
|
355
|
+
// registration must never fail the build.
|
|
356
|
+
watchRegistrationUnsupported = true;
|
|
357
|
+
logger.warn(`Registering additional watch files is not supported by this bundler context — skipping further registrations (${error?.message ?? String(error)})`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Whether a config file was present at the last load — drives the
|
|
361
|
+
// one-time warning when every config candidate disappears.
|
|
362
|
+
let hadActiveConfig = false;
|
|
363
|
+
// Watch-rebuild serialization state (see watchChange).
|
|
364
|
+
let compiling = false;
|
|
365
|
+
const pendingWatchPaths = new Set();
|
|
366
|
+
// Options from the most recent successful resolution — reused by input
|
|
367
|
+
// events that cannot have changed them.
|
|
368
|
+
let lastOptions;
|
|
369
|
+
// Set once a bundler context rejects watch-file registrations (esbuild);
|
|
370
|
+
// further registration attempts are skipped to avoid warning floods.
|
|
371
|
+
let watchRegistrationUnsupported = false;
|
|
372
|
+
const getProjectDir = () => nodeNormalizePath(resolve(discoveryRoot ?? process.cwd(), userArgs.project));
|
|
373
|
+
const resolveOptionsWith = (loaded) => resolveCompilerOptions({
|
|
374
|
+
config: loaded?.config,
|
|
375
|
+
overrides: userArgs,
|
|
376
|
+
root: discoveryRoot ?? process.cwd(),
|
|
377
|
+
});
|
|
378
|
+
/**
|
|
379
|
+
* Loads the active config, following candidate renames and keeping the
|
|
380
|
+
* last known-good configuration while the file is missing or invalid.
|
|
381
|
+
* Callers that know the config content changed should clear the cache
|
|
382
|
+
* for the project directory first.
|
|
383
|
+
*/
|
|
384
|
+
async function loadActiveConfig() {
|
|
385
|
+
const dir = getProjectDir();
|
|
386
|
+
const winner = await resolveConfigCandidate(dir);
|
|
387
|
+
if (winner === undefined) {
|
|
388
|
+
if (hadActiveConfig) {
|
|
389
|
+
hadActiveConfig = false;
|
|
390
|
+
logger.warn("Paraglide config file was removed, continuing with default options.");
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
hadActiveConfig = true;
|
|
395
|
+
// Throws when the file exists but is invalid — callers decide the
|
|
396
|
+
// policy: watch modes skip and surface the error in development,
|
|
397
|
+
// build paths fail loudly in production.
|
|
398
|
+
return loadParaglideConfig({ projectDir: dir, logger });
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Rebuild for a single watch event. Config-named events reload the
|
|
402
|
+
* config; input events reuse the last known options — options cannot
|
|
403
|
+
* have changed without a config event.
|
|
404
|
+
*/
|
|
405
|
+
async function performWatchRecompile(path) {
|
|
406
|
+
const normalizedPath = nodeNormalizePath(path);
|
|
407
|
+
const projectDir = getProjectDir();
|
|
408
|
+
const withinProjectDir = normalizedPath === projectDir ||
|
|
409
|
+
normalizedPath.startsWith(`${projectDir}/`);
|
|
410
|
+
const configEvent = withinProjectDir && isConfigFileName(normalizedPath);
|
|
411
|
+
let args;
|
|
412
|
+
if (!configEvent && lastOptions !== undefined) {
|
|
413
|
+
args = lastOptions;
|
|
414
|
+
const targets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
415
|
+
if (targets.isIgnoredPath(normalizedPath))
|
|
416
|
+
return;
|
|
417
|
+
const shouldCompile = targets.files.has(normalizedPath) ||
|
|
418
|
+
isPathWithinDirectories(normalizedPath, targets.directories);
|
|
419
|
+
if (!shouldCompile)
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (configEvent) {
|
|
424
|
+
logger.info("Paraglide configuration changed, re-compiling.");
|
|
425
|
+
clearParaglideConfigCache({ projectDir });
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const loaded = await loadActiveConfig();
|
|
429
|
+
args = resolveOptionsWith(loaded);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
// Invalid config: skip the compilation entirely and surface
|
|
433
|
+
// the problem. The previously compiled output stays on disk.
|
|
434
|
+
logger.error(error.message);
|
|
435
|
+
logger.error("Skipping compilation because of the invalid paraglide config.");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
lastOptions = args;
|
|
439
|
+
}
|
|
440
|
+
maybeInvalidateForOptionsChange(state, args);
|
|
441
|
+
const outputStructure = resolveOutputStructure(args);
|
|
442
|
+
const previouslyReadFiles = new Set(readFiles);
|
|
443
|
+
try {
|
|
444
|
+
if (!configEvent) {
|
|
445
|
+
logger.info(`Re-compiling inlang project... File "${relative(process.cwd(), path)}" has changed.`);
|
|
446
|
+
}
|
|
447
|
+
clearReadFiles();
|
|
448
|
+
await compileAndUpdateState({
|
|
449
|
+
state,
|
|
450
|
+
compilerOptions: args,
|
|
451
|
+
outputStructure,
|
|
452
|
+
previousCompilation: state.previousCompilation,
|
|
453
|
+
isServer,
|
|
454
|
+
});
|
|
455
|
+
await writePersistentCache(state, args, outputStructure);
|
|
456
|
+
logger.success(`Re-compilation complete (${outputStructure})`);
|
|
457
|
+
const nextTargets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
458
|
+
for (const filePath of nextTargets.files)
|
|
459
|
+
safeAddWatchFile(undefined, filePath);
|
|
460
|
+
for (const directoryPath of nextTargets.directories)
|
|
461
|
+
safeAddWatchFile(undefined, directoryPath);
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
clearReadFiles();
|
|
465
|
+
for (const filePath of previouslyReadFiles)
|
|
466
|
+
readFiles.add(filePath);
|
|
467
|
+
state.previousCompilation = undefined;
|
|
468
|
+
state.previousInputsDigest = undefined;
|
|
469
|
+
logger.warn("Failed to re-compile project:", e.message);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
228
472
|
return {
|
|
229
473
|
name: PLUGIN_NAME,
|
|
230
474
|
enforce: "pre",
|
|
231
475
|
async buildStart() {
|
|
232
|
-
|
|
233
|
-
// default to locale-modules for development to speed up the dev server
|
|
234
|
-
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
235
|
-
const outputStructure = args.outputStructure ??
|
|
236
|
-
(isProduction ? "message-modules" : "locale-modules");
|
|
476
|
+
addWatchFile = this.addWatchFile?.bind(this);
|
|
237
477
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
(await restorePersistentCache({
|
|
243
|
-
state,
|
|
244
|
-
compilerOptions: args,
|
|
245
|
-
outputStructure,
|
|
246
|
-
}))) {
|
|
247
|
-
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
// `vite build` calls buildStart once per environment (client, ssr).
|
|
251
|
-
// Skip the expensive compile when the inputs haven't changed.
|
|
252
|
-
if (state.previousCompilation && state.previousInputsDigest) {
|
|
253
|
-
const currentDigest = await computeInputsDigest(state, args, outputStructure);
|
|
254
|
-
if (currentDigest === state.previousInputsDigest) {
|
|
255
|
-
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// On a fresh process, seed previousCompilation from on-disk hashes
|
|
260
|
-
// so the first compile is a no-op when inputs are unchanged. Avoids
|
|
261
|
-
// racing concurrent readers that wiping outdir would interrupt.
|
|
262
|
-
const seededPrevious = state.previousCompilation ??
|
|
263
|
-
(await seedPreviousCompilationFromOutdir({
|
|
264
|
-
outdir: args.outdir,
|
|
265
|
-
fs: args.fs?.promises,
|
|
266
|
-
}));
|
|
267
|
-
state.previousCompilation = await compile({
|
|
268
|
-
previousCompilation: seededPrevious,
|
|
269
|
-
outputStructure,
|
|
270
|
-
isServer,
|
|
271
|
-
...withoutCleanOutdir(args),
|
|
272
|
-
cleanOutdir: false,
|
|
273
|
-
// after the args spread so a user-provided fs doesn't bypass
|
|
274
|
-
// the read tracking (trackedFs wraps args.fs when provided)
|
|
275
|
-
fs: trackedFs,
|
|
276
|
-
});
|
|
277
|
-
state.previousInputsDigest = await computeInputsDigest(state, args, outputStructure);
|
|
278
|
-
await writePersistentCache(state, args, outputStructure);
|
|
279
|
-
logger.success(`Compilation complete (${outputStructure})`);
|
|
478
|
+
const loaded = await loadActiveConfig();
|
|
479
|
+
const args = resolveOptionsWith(loaded);
|
|
480
|
+
lastOptions = args;
|
|
481
|
+
await runCompilation({ state, compilerOptions: args });
|
|
280
482
|
}
|
|
281
483
|
catch (error) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
484
|
+
// Config errors are converted to last-good options by
|
|
485
|
+
// loadActiveConfig; compilation errors are handled inside
|
|
486
|
+
// runCompilation. Anything escaping is unexpected — fail the
|
|
487
|
+
// build in production, keep serving in development.
|
|
488
|
+
if (process.env.NODE_ENV === "production")
|
|
286
489
|
throw error;
|
|
490
|
+
logger.error("Failed to prepare paraglide compilation:", error.message);
|
|
491
|
+
return;
|
|
287
492
|
}
|
|
288
493
|
finally {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
494
|
+
if (lastOptions !== undefined) {
|
|
495
|
+
const targets = getWatchTargets(readFiles, {
|
|
496
|
+
outdir: lastOptions.outdir,
|
|
497
|
+
});
|
|
498
|
+
for (const filePath of targets.files)
|
|
499
|
+
safeAddWatchFile(this, filePath);
|
|
500
|
+
for (const directoryPath of targets.directories)
|
|
501
|
+
safeAddWatchFile(this, directoryPath);
|
|
293
502
|
}
|
|
294
|
-
|
|
295
|
-
|
|
503
|
+
// Register every config candidate inside the project directory
|
|
504
|
+
// so renames and format switches are reported even by backends
|
|
505
|
+
// that only report watched files (webpack's modifiedFiles).
|
|
506
|
+
const projectDir = getProjectDir();
|
|
507
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
508
|
+
safeAddWatchFile(this, join(projectDir, name));
|
|
296
509
|
}
|
|
297
510
|
}
|
|
298
511
|
},
|
|
299
512
|
async watchChange(path) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
513
|
+
// Serialize rebuilds: bundlers may deliver several watchChange
|
|
514
|
+
// events concurrently (webpack fires once per modified file).
|
|
515
|
+
// Every path is queued — a config event must never be starved by
|
|
516
|
+
// later input events.
|
|
517
|
+
pendingWatchPaths.add(nodeNormalizePath(path));
|
|
518
|
+
if (compiling)
|
|
303
519
|
return;
|
|
304
|
-
|
|
305
|
-
const shouldCompile = targets.files.has(normalizedPath) ||
|
|
306
|
-
isPathWithinDirectories(normalizedPath, targets.directories);
|
|
307
|
-
if (shouldCompile === false) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
311
|
-
// default to locale-modules for development to speed up the dev server
|
|
312
|
-
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
313
|
-
const outputStructure = args.outputStructure ??
|
|
314
|
-
(isProduction ? "message-modules" : "locale-modules");
|
|
315
|
-
const previouslyReadFiles = new Set(readFiles);
|
|
520
|
+
compiling = true;
|
|
316
521
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
isServer,
|
|
324
|
-
...withoutCleanOutdir(args),
|
|
325
|
-
cleanOutdir: false,
|
|
326
|
-
fs: trackedFs,
|
|
327
|
-
});
|
|
328
|
-
state.previousInputsDigest = await computeInputsDigest(state, args, outputStructure);
|
|
329
|
-
await writePersistentCache(state, args, outputStructure);
|
|
330
|
-
logger.success(`Re-compilation complete (${outputStructure})`);
|
|
331
|
-
// Add any new files to watch
|
|
332
|
-
const nextTargets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
333
|
-
for (const filePath of nextTargets.files) {
|
|
334
|
-
this.addWatchFile(filePath);
|
|
335
|
-
}
|
|
336
|
-
for (const directoryPath of nextTargets.directories) {
|
|
337
|
-
this.addWatchFile(directoryPath);
|
|
522
|
+
while (pendingWatchPaths.size > 0) {
|
|
523
|
+
const batch = [...pendingWatchPaths];
|
|
524
|
+
pendingWatchPaths.clear();
|
|
525
|
+
for (const current of batch) {
|
|
526
|
+
await performWatchRecompile(current);
|
|
527
|
+
}
|
|
338
528
|
}
|
|
339
529
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
for (const filePath of previouslyReadFiles) {
|
|
343
|
-
readFiles.add(filePath);
|
|
344
|
-
}
|
|
345
|
-
// Reset compilation result on error
|
|
346
|
-
state.previousCompilation = undefined;
|
|
347
|
-
state.previousInputsDigest = undefined;
|
|
348
|
-
logger.warn("Failed to re-compile project:", e.message);
|
|
530
|
+
finally {
|
|
531
|
+
compiling = false;
|
|
349
532
|
}
|
|
350
533
|
},
|
|
351
534
|
vite: {
|
|
@@ -359,8 +542,14 @@ export const unpluginFactory = (args) => {
|
|
|
359
542
|
isServer = "import.meta.env?.SSR ?? typeof window === 'undefined'";
|
|
360
543
|
},
|
|
361
544
|
},
|
|
545
|
+
configResolved: {
|
|
546
|
+
handler: (config) => {
|
|
547
|
+
discoveryRoot = config.root;
|
|
548
|
+
},
|
|
549
|
+
},
|
|
362
550
|
},
|
|
363
551
|
webpack(compiler) {
|
|
552
|
+
discoveryRoot = compiler.context;
|
|
364
553
|
compiler.options.resolve = {
|
|
365
554
|
...compiler.options.resolve,
|
|
366
555
|
fallback: {
|
|
@@ -370,41 +559,133 @@ export const unpluginFactory = (args) => {
|
|
|
370
559
|
},
|
|
371
560
|
};
|
|
372
561
|
compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => {
|
|
373
|
-
|
|
374
|
-
// default to locale-modules for development to speed up the dev server
|
|
375
|
-
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
376
|
-
const outputStructure = args.outputStructure ??
|
|
377
|
-
(isProduction ? "message-modules" : "locale-modules");
|
|
562
|
+
let args;
|
|
378
563
|
try {
|
|
564
|
+
const loaded = await loadActiveConfig();
|
|
565
|
+
args = resolveOptionsWith(loaded);
|
|
566
|
+
lastOptions = args;
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
state.previousInputsDigest = undefined;
|
|
570
|
+
state.previousCompilation = undefined;
|
|
571
|
+
logger.error("Failed to load paraglide config:", error.message);
|
|
572
|
+
if (process.env.NODE_ENV === "production")
|
|
573
|
+
throw error;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const outputStructure = resolveOutputStructure(args);
|
|
577
|
+
maybeInvalidateForOptionsChange(state, args);
|
|
578
|
+
try {
|
|
579
|
+
await preparePersistentCacheDirectory(args, outputStructure);
|
|
580
|
+
if (state.previousCompilation === undefined &&
|
|
581
|
+
(await restorePersistentCache({
|
|
582
|
+
state,
|
|
583
|
+
compilerOptions: args,
|
|
584
|
+
outputStructure,
|
|
585
|
+
}))) {
|
|
586
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
379
589
|
// Multi-compiler webpack setups (client + server) trigger
|
|
380
590
|
// beforeRun once per compiler — skip when inputs are unchanged.
|
|
381
|
-
if (
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
591
|
+
if (await hasUnchangedInputs({
|
|
592
|
+
state,
|
|
593
|
+
compilerOptions: args,
|
|
594
|
+
outputStructure,
|
|
595
|
+
})) {
|
|
596
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
597
|
+
return;
|
|
387
598
|
}
|
|
388
599
|
const seededPrevious = state.previousCompilation ??
|
|
389
600
|
(await seedPreviousCompilationFromOutdir({
|
|
390
601
|
outdir: args.outdir,
|
|
391
602
|
fs: args.fs?.promises,
|
|
392
603
|
}));
|
|
393
|
-
|
|
604
|
+
await compileAndUpdateState({
|
|
605
|
+
state,
|
|
606
|
+
compilerOptions: args,
|
|
607
|
+
outputStructure,
|
|
394
608
|
previousCompilation: seededPrevious,
|
|
609
|
+
// webpack has no vite-style SSR expression; rely on the
|
|
610
|
+
// compiler default.
|
|
611
|
+
isServer: undefined,
|
|
612
|
+
});
|
|
613
|
+
await writePersistentCache(state, args, outputStructure);
|
|
614
|
+
logger.success(`Compilation complete (${outputStructure})`);
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
state.previousInputsDigest = undefined;
|
|
618
|
+
logger.warn("Failed to compile project:", error.message);
|
|
619
|
+
logger.warn("Please check your translation files for syntax errors.");
|
|
620
|
+
if (process.env.NODE_ENV === "production")
|
|
621
|
+
throw error;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
rspack(compiler) {
|
|
626
|
+
discoveryRoot = compiler.context;
|
|
627
|
+
// Rspack mirrors webpack's hook API but ships no usable types,
|
|
628
|
+
// so hooks are tapped structurally.
|
|
629
|
+
const tapRspackBeforeRun = (fn) => {
|
|
630
|
+
const target = compiler.hooks?.beforeRun;
|
|
631
|
+
target?.tapPromise?.(PLUGIN_NAME, fn);
|
|
632
|
+
};
|
|
633
|
+
tapRspackBeforeRun(async () => {
|
|
634
|
+
let args;
|
|
635
|
+
try {
|
|
636
|
+
const loaded = await loadActiveConfig();
|
|
637
|
+
args = resolveOptionsWith(loaded);
|
|
638
|
+
lastOptions = args;
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
state.previousInputsDigest = undefined;
|
|
642
|
+
state.previousCompilation = undefined;
|
|
643
|
+
logger.error("Failed to load paraglide config:", error.message);
|
|
644
|
+
if (process.env.NODE_ENV === "production")
|
|
645
|
+
throw error;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const outputStructure = resolveOutputStructure(args);
|
|
649
|
+
maybeInvalidateForOptionsChange(state, args);
|
|
650
|
+
try {
|
|
651
|
+
await preparePersistentCacheDirectory(args, outputStructure);
|
|
652
|
+
if (state.previousCompilation === undefined &&
|
|
653
|
+
(await restorePersistentCache({
|
|
654
|
+
state,
|
|
655
|
+
compilerOptions: args,
|
|
656
|
+
outputStructure,
|
|
657
|
+
}))) {
|
|
658
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (await hasUnchangedInputs({
|
|
662
|
+
state,
|
|
663
|
+
compilerOptions: args,
|
|
395
664
|
outputStructure,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
665
|
+
})) {
|
|
666
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const seededPrevious = state.previousCompilation ??
|
|
670
|
+
(await seedPreviousCompilationFromOutdir({
|
|
671
|
+
outdir: args.outdir,
|
|
672
|
+
fs: args.fs?.promises,
|
|
673
|
+
}));
|
|
674
|
+
await compileAndUpdateState({
|
|
675
|
+
state,
|
|
676
|
+
compilerOptions: args,
|
|
677
|
+
outputStructure,
|
|
678
|
+
previousCompilation: seededPrevious,
|
|
679
|
+
isServer: undefined,
|
|
399
680
|
});
|
|
400
|
-
|
|
681
|
+
await writePersistentCache(state, args, outputStructure);
|
|
401
682
|
logger.success(`Compilation complete (${outputStructure})`);
|
|
402
683
|
}
|
|
403
684
|
catch (error) {
|
|
404
685
|
state.previousInputsDigest = undefined;
|
|
405
686
|
logger.warn("Failed to compile project:", error.message);
|
|
406
687
|
logger.warn("Please check your translation files for syntax errors.");
|
|
407
|
-
if (
|
|
688
|
+
if (process.env.NODE_ENV === "production")
|
|
408
689
|
throw error;
|
|
409
690
|
}
|
|
410
691
|
});
|