gofront 1.5.1 → 1.6.0
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/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
package/src/cli-core.js
CHANGED
|
@@ -44,11 +44,25 @@ export function runCompile(inputPath, isDir, options) {
|
|
|
44
44
|
dumpTokens = false,
|
|
45
45
|
dumpAst = false,
|
|
46
46
|
emitWat = false,
|
|
47
|
+
wasmOpt = false,
|
|
48
|
+
forceTarget = null,
|
|
49
|
+
targetOverrides = null,
|
|
50
|
+
rootDir = null,
|
|
47
51
|
} = options ?? {};
|
|
52
|
+
const targetOpts = {};
|
|
53
|
+
if (forceTarget) targetOpts.forceTarget = forceTarget;
|
|
54
|
+
if (targetOverrides) targetOpts.targetOverrides = targetOverrides;
|
|
55
|
+
if (rootDir) targetOpts.rootDir = rootDir;
|
|
48
56
|
|
|
49
57
|
if (isDir) {
|
|
50
58
|
const outputDir = outputFile ? dirname(resolve(outputFile)) : resolve(".");
|
|
51
|
-
return compileDir(inputPath, {
|
|
59
|
+
return compileDir(inputPath, {
|
|
60
|
+
sourceMap,
|
|
61
|
+
outputDir,
|
|
62
|
+
emitWat,
|
|
63
|
+
wasmOpt,
|
|
64
|
+
...targetOpts,
|
|
65
|
+
});
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
return compileSingleFile(inputPath, {
|
|
@@ -57,9 +71,68 @@ export function runCompile(inputPath, isDir, options) {
|
|
|
57
71
|
dumpTokens,
|
|
58
72
|
dumpAst,
|
|
59
73
|
emitWat,
|
|
74
|
+
wasmOpt,
|
|
75
|
+
...targetOpts,
|
|
60
76
|
});
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
// `--js-only` / `--target js` compile every package to JavaScript (A/B
|
|
80
|
+
// benchmarking, fallback builds for browsers without WasmGC).
|
|
81
|
+
export function parseTargetFlag(argv) {
|
|
82
|
+
if (argv.includes("--js-only")) return "js";
|
|
83
|
+
for (let i = 0; i < argv.length; i++) {
|
|
84
|
+
const arg = argv[i];
|
|
85
|
+
const value =
|
|
86
|
+
arg === "--target"
|
|
87
|
+
? argv[i + 1]
|
|
88
|
+
: arg.startsWith("--target=")
|
|
89
|
+
? arg.slice("--target=".length)
|
|
90
|
+
: null;
|
|
91
|
+
if (value === null || value === undefined) continue;
|
|
92
|
+
if (value !== "js")
|
|
93
|
+
throw new Error(
|
|
94
|
+
`unsupported --target '${value}' (only 'js' is supported; package targets come from //gofront:target directives or gofront.json "targets")`,
|
|
95
|
+
);
|
|
96
|
+
return "js";
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Per-package target overrides from `gofront.json` (`"targets": { "engine/physics": "wasm" }`).
|
|
102
|
+
function projectTargetOverrides(project) {
|
|
103
|
+
const t = project?.config?.targets;
|
|
104
|
+
return t && typeof t === "object" ? t : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Override keys are relative to the project's source root, so every command
|
|
108
|
+
// (build, dev, check, test — including `dir/...`) must resolve them against
|
|
109
|
+
// the same `rootDir` or a sub-package would see itself as ".".
|
|
110
|
+
function projectTargetOptions(project, options = {}) {
|
|
111
|
+
const targetOverrides =
|
|
112
|
+
options.targetOverrides ?? projectTargetOverrides(project);
|
|
113
|
+
if (!targetOverrides) return {};
|
|
114
|
+
return {
|
|
115
|
+
targetOverrides,
|
|
116
|
+
rootDir: options.rootDir ?? project.srcDir,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Removes `--target <value>` / `--target=<value>` / `--js-only` so the value
|
|
121
|
+
// is not mistaken for a positional argument.
|
|
122
|
+
function stripTargetFlag(argv) {
|
|
123
|
+
const out = [];
|
|
124
|
+
for (let i = 0; i < argv.length; i++) {
|
|
125
|
+
const arg = argv[i];
|
|
126
|
+
if (arg === "--target") {
|
|
127
|
+
i++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (arg.startsWith("--target=") || arg === "--js-only") continue;
|
|
131
|
+
out.push(arg);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
63
136
|
// Writes the JS bundle plus, when the project links wasm packages, the
|
|
64
137
|
// sibling `app.wasm` (and `app.wat` when requested). Stale wasm artifacts
|
|
65
138
|
// are removed so a project that drops its wasm packages serves clean output —
|
|
@@ -70,15 +143,23 @@ export function writeCompileOutput(outputFile, result, js = result.js) {
|
|
|
70
143
|
writeFileSync(outputFile, `${js}\n`);
|
|
71
144
|
const wasmFile = join(dirname(outputFile), "app.wasm");
|
|
72
145
|
const watFile = join(dirname(outputFile), "app.wat");
|
|
146
|
+
const wasmMapFile = join(dirname(outputFile), "app.wasm.map");
|
|
73
147
|
const written = [outputFile];
|
|
74
148
|
let ownsWasm = Boolean(result.wasm);
|
|
75
149
|
if (result.wasm) {
|
|
76
150
|
writeFileSync(wasmFile, result.wasm);
|
|
77
151
|
written.push(wasmFile);
|
|
152
|
+
if (result.wasmSourceMap) {
|
|
153
|
+
writeFileSync(wasmMapFile, result.wasmSourceMap);
|
|
154
|
+
written.push(wasmMapFile);
|
|
155
|
+
} else if (existsSync(wasmMapFile)) {
|
|
156
|
+
rmSync(wasmMapFile);
|
|
157
|
+
}
|
|
78
158
|
} else if (existsSync(wasmFile)) {
|
|
79
159
|
if (isGoFrontWasm(readFileSync(wasmFile))) {
|
|
80
160
|
rmSync(wasmFile);
|
|
81
161
|
ownsWasm = true;
|
|
162
|
+
if (existsSync(wasmMapFile)) rmSync(wasmMapFile);
|
|
82
163
|
}
|
|
83
164
|
}
|
|
84
165
|
if (result.wat) {
|
|
@@ -254,10 +335,11 @@ export function formatPrepSummary({ assets, vendor }) {
|
|
|
254
335
|
export function parseTestArgs(argv) {
|
|
255
336
|
let run = null;
|
|
256
337
|
const positional = [];
|
|
257
|
-
|
|
258
|
-
|
|
338
|
+
const rest = stripTargetFlag(argv);
|
|
339
|
+
for (let i = 0; i < rest.length; i++) {
|
|
340
|
+
const arg = rest[i];
|
|
259
341
|
if (arg === "-run" || arg === "--run") {
|
|
260
|
-
run =
|
|
342
|
+
run = rest[++i] ?? null;
|
|
261
343
|
} else if (arg.startsWith("-run=") || arg.startsWith("--run=")) {
|
|
262
344
|
run = arg.slice(arg.indexOf("=") + 1);
|
|
263
345
|
} else if (!arg.startsWith("-")) {
|
|
@@ -269,16 +351,24 @@ export function parseTestArgs(argv) {
|
|
|
269
351
|
verbose: argv.includes("-v"),
|
|
270
352
|
dom: argv.includes("--dom"),
|
|
271
353
|
run,
|
|
354
|
+
forceTarget: parseTargetFlag(argv),
|
|
272
355
|
};
|
|
273
356
|
}
|
|
274
357
|
|
|
275
358
|
export function handleTest(targetDir = ".", options = {}) {
|
|
276
359
|
const pattern = parsePackagePattern(targetDir);
|
|
277
|
-
if (pattern)
|
|
360
|
+
if (pattern) {
|
|
361
|
+
const project = detectProject(pattern);
|
|
362
|
+
return runTestsRecursive(pattern, {
|
|
363
|
+
...options,
|
|
364
|
+
...projectTargetOptions(project, options),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
278
367
|
|
|
279
368
|
let testTarget = targetDir;
|
|
369
|
+
let project = null;
|
|
280
370
|
try {
|
|
281
|
-
|
|
371
|
+
project = detectProject(targetDir);
|
|
282
372
|
const targetPath = resolve(targetDir);
|
|
283
373
|
if (
|
|
284
374
|
statSync(targetPath).isDirectory() &&
|
|
@@ -290,7 +380,10 @@ export function handleTest(targetDir = ".", options = {}) {
|
|
|
290
380
|
: dirname(project.srcDir);
|
|
291
381
|
}
|
|
292
382
|
} catch {}
|
|
293
|
-
return runTests(testTarget,
|
|
383
|
+
return runTests(testTarget, {
|
|
384
|
+
...options,
|
|
385
|
+
...projectTargetOptions(project, options),
|
|
386
|
+
});
|
|
294
387
|
}
|
|
295
388
|
|
|
296
389
|
async function runTestsRecursive(rootDir, options) {
|
|
@@ -318,7 +411,7 @@ async function runTestsRecursive(rootDir, options) {
|
|
|
318
411
|
|
|
319
412
|
// Returns the root directory of a Go-style recursive pattern (`./...`,
|
|
320
413
|
// `app/src/...`), or null when the argument is a plain path.
|
|
321
|
-
|
|
414
|
+
function parsePackagePattern(arg) {
|
|
322
415
|
if (arg === "...") return resolve(".");
|
|
323
416
|
if (!arg.endsWith("/...")) return null;
|
|
324
417
|
return resolve(arg.slice(0, -4) || ".");
|
|
@@ -328,7 +421,7 @@ const SKIPPED_DIRS = new Set(["node_modules", "dist", "public"]);
|
|
|
328
421
|
|
|
329
422
|
// Lists every directory under root (inclusive) that contains .go or .templ
|
|
330
423
|
// files, in sorted order, skipping hidden directories and build output.
|
|
331
|
-
|
|
424
|
+
function findPackageDirs(root) {
|
|
332
425
|
const out = [];
|
|
333
426
|
const walk = (dir) => {
|
|
334
427
|
let entries;
|
|
@@ -418,7 +511,7 @@ function resolveDevOutputFile(projectRoot, serveDir, config) {
|
|
|
418
511
|
return join(serveDir, "app.js");
|
|
419
512
|
}
|
|
420
513
|
|
|
421
|
-
|
|
514
|
+
function detectProject(dir = ".") {
|
|
422
515
|
const projectRoot = resolve(dir);
|
|
423
516
|
const config = loadProjectConfig(projectRoot);
|
|
424
517
|
const srcDir = resolveSrcDir(projectRoot, config);
|
|
@@ -441,9 +534,10 @@ export function detectProject(dir = ".") {
|
|
|
441
534
|
// ── check command ─────────────────────────────────────────────
|
|
442
535
|
|
|
443
536
|
export function parseCheckArgs(argv) {
|
|
444
|
-
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
537
|
+
const positional = stripTargetFlag(argv).filter((a) => !a.startsWith("-"));
|
|
445
538
|
return {
|
|
446
539
|
targetDir: positional[0] ?? ".",
|
|
540
|
+
forceTarget: parseTargetFlag(argv),
|
|
447
541
|
};
|
|
448
542
|
}
|
|
449
543
|
|
|
@@ -454,11 +548,12 @@ export function handleCheck(targetDir = ".", options = {}) {
|
|
|
454
548
|
if (dirs.length === 0) {
|
|
455
549
|
throw new Error(`No .go files found under ${targetDir}`);
|
|
456
550
|
}
|
|
551
|
+
const targetOpts = projectTargetOptions(detectProject(pattern), options);
|
|
457
552
|
const startMs = performance.now();
|
|
458
553
|
const packages = [];
|
|
459
554
|
for (const dir of dirs) {
|
|
460
555
|
const pkgStart = performance.now();
|
|
461
|
-
runCompile(dir, true, { ...options });
|
|
556
|
+
runCompile(dir, true, { ...options, ...targetOpts });
|
|
462
557
|
packages.push({
|
|
463
558
|
dir,
|
|
464
559
|
elapsedMs: (performance.now() - pkgStart).toFixed(0),
|
|
@@ -488,7 +583,10 @@ export function handleCheck(targetDir = ".", options = {}) {
|
|
|
488
583
|
|
|
489
584
|
const targetIsDir = statSync(compileTarget).isDirectory();
|
|
490
585
|
const startMs = performance.now();
|
|
491
|
-
runCompile(compileTarget, targetIsDir, {
|
|
586
|
+
runCompile(compileTarget, targetIsDir, {
|
|
587
|
+
...options,
|
|
588
|
+
...projectTargetOptions(project, options),
|
|
589
|
+
});
|
|
492
590
|
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
493
591
|
return { target: targetDir, compileTarget, elapsedMs };
|
|
494
592
|
}
|
|
@@ -503,6 +601,10 @@ export function parseBuildArgs(argv) {
|
|
|
503
601
|
const noMinify = argv.includes("--no-minify");
|
|
504
602
|
const noMangle = argv.includes("--no-mangle");
|
|
505
603
|
const emitWat = argv.includes("--emit-wat");
|
|
604
|
+
const release = argv.includes("--release");
|
|
605
|
+
const noWasmOpt = argv.includes("--no-wasm-opt");
|
|
606
|
+
const wasmOpt = !noWasmOpt && (argv.includes("--wasm-opt") || release);
|
|
607
|
+
const forceTarget = parseTargetFlag(argv);
|
|
506
608
|
|
|
507
609
|
for (let i = 0; i < argv.length; i++) {
|
|
508
610
|
const arg = argv[i];
|
|
@@ -510,6 +612,8 @@ export function parseBuildArgs(argv) {
|
|
|
510
612
|
outDir = argv[++i] ?? null;
|
|
511
613
|
} else if (arg.startsWith("--out-dir=")) {
|
|
512
614
|
outDir = arg.slice(arg.indexOf("=") + 1);
|
|
615
|
+
} else if (arg === "--target") {
|
|
616
|
+
i++;
|
|
513
617
|
} else if (!arg.startsWith("-")) {
|
|
514
618
|
positional.push(arg);
|
|
515
619
|
}
|
|
@@ -523,6 +627,9 @@ export function parseBuildArgs(argv) {
|
|
|
523
627
|
emitWat,
|
|
524
628
|
minify: !noMinify,
|
|
525
629
|
mangle: !noMangle && !noMinify,
|
|
630
|
+
release,
|
|
631
|
+
wasmOpt,
|
|
632
|
+
forceTarget,
|
|
526
633
|
};
|
|
527
634
|
}
|
|
528
635
|
|
|
@@ -684,12 +791,19 @@ function collectOutputFiles(outDir) {
|
|
|
684
791
|
return files;
|
|
685
792
|
}
|
|
686
793
|
|
|
687
|
-
export function formatBuildSummary({ outDir, elapsedMs, files, pwa }) {
|
|
794
|
+
export function formatBuildSummary({ outDir, elapsedMs, files, pwa, wasmOpt }) {
|
|
688
795
|
const lines = [`build complete in ${elapsedMs}ms → ${outDir}`];
|
|
689
796
|
for (const f of files) {
|
|
690
797
|
const sizeKb = (f.size / 1024).toFixed(1);
|
|
691
798
|
lines.push(` ${f.name} (${sizeKb} kB)`);
|
|
692
799
|
}
|
|
800
|
+
if (wasmOpt && wasmOpt.originalSize > 0) {
|
|
801
|
+
const origKb = (wasmOpt.originalSize / 1024).toFixed(1);
|
|
802
|
+
const optKb = (wasmOpt.optimizedSize / 1024).toFixed(1);
|
|
803
|
+
lines.push(
|
|
804
|
+
` wasm-opt: app.wasm ${origKb} kB → ${optKb} kB (-${wasmOpt.percentSaved}%) [${wasmOpt.engine}]`,
|
|
805
|
+
);
|
|
806
|
+
}
|
|
693
807
|
if (pwa) {
|
|
694
808
|
lines.push(
|
|
695
809
|
` pwa: generated sw.js (${pwa.assetsCount} cached assets, ${pwa.version})`,
|
|
@@ -712,6 +826,9 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
712
826
|
outputFile,
|
|
713
827
|
sourceMap: options.sourceMap ?? false,
|
|
714
828
|
emitWat: options.emitWat ?? false,
|
|
829
|
+
wasmOpt: options.wasmOpt ?? false,
|
|
830
|
+
forceTarget: options.forceTarget ?? null,
|
|
831
|
+
...projectTargetOptions(project, options),
|
|
715
832
|
});
|
|
716
833
|
|
|
717
834
|
const doMinify = options.minify ?? true;
|
|
@@ -757,16 +874,45 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
757
874
|
assets,
|
|
758
875
|
vendor,
|
|
759
876
|
pwa,
|
|
877
|
+
wasmOpt: compileResult.wasmOptInfo,
|
|
760
878
|
};
|
|
761
879
|
}
|
|
762
880
|
|
|
763
881
|
export { generatePwa };
|
|
764
882
|
|
|
883
|
+
// ── legacy `gofront <input> [flags]` command ──────────────────
|
|
884
|
+
|
|
885
|
+
export function parseLegacyArgs(argv) {
|
|
886
|
+
const outputFlag = argv.indexOf("-o");
|
|
887
|
+
const portFlag = argv.indexOf("--port");
|
|
888
|
+
const serve = argv.includes("--serve");
|
|
889
|
+
const release = argv.includes("--release");
|
|
890
|
+
return {
|
|
891
|
+
inputArg: argv[0],
|
|
892
|
+
outputFile: outputFlag !== -1 ? (argv[outputFlag + 1] ?? null) : null,
|
|
893
|
+
checkOnly: argv.includes("--check"),
|
|
894
|
+
dumpAst: argv.includes("--ast"),
|
|
895
|
+
dumpTokens: argv.includes("--tokens"),
|
|
896
|
+
sourceMap: argv.includes("--source-map"),
|
|
897
|
+
forceTarget: parseTargetFlag(argv),
|
|
898
|
+
serve,
|
|
899
|
+
watch: argv.includes("--watch") || serve,
|
|
900
|
+
copyAssets: argv.includes("--copy-assets"),
|
|
901
|
+
release,
|
|
902
|
+
wasmOpt:
|
|
903
|
+
!argv.includes("--no-wasm-opt") &&
|
|
904
|
+
(argv.includes("--wasm-opt") || release),
|
|
905
|
+
minify: argv.includes("--minify") || release,
|
|
906
|
+
mangle: argv.includes("--mangle") || release,
|
|
907
|
+
port: portFlag !== -1 ? parseInt(argv[portFlag + 1], 10) : 3000,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
765
911
|
// ── dev command ───────────────────────────────────────────────
|
|
766
912
|
|
|
767
913
|
export function parseDevArgs(argv) {
|
|
768
914
|
let port = null;
|
|
769
|
-
let outputFile
|
|
915
|
+
let outputFile; // undefined → fall back to project.devOutputFile
|
|
770
916
|
const positional = [];
|
|
771
917
|
for (let i = 0; i < argv.length; i++) {
|
|
772
918
|
const arg = argv[i];
|
|
@@ -775,7 +921,9 @@ export function parseDevArgs(argv) {
|
|
|
775
921
|
} else if (arg.startsWith("--port=")) {
|
|
776
922
|
port = parseInt(arg.slice(arg.indexOf("=") + 1), 10);
|
|
777
923
|
} else if (arg === "-o" || arg === "--output") {
|
|
778
|
-
outputFile = argv[++i]
|
|
924
|
+
outputFile = argv[++i];
|
|
925
|
+
} else if (arg === "--target") {
|
|
926
|
+
i++;
|
|
779
927
|
} else if (!arg.startsWith("-")) {
|
|
780
928
|
positional.push(arg);
|
|
781
929
|
}
|
|
@@ -784,29 +932,58 @@ export function parseDevArgs(argv) {
|
|
|
784
932
|
targetDir: positional[0] ?? ".",
|
|
785
933
|
port: Number.isNaN(port) ? null : port,
|
|
786
934
|
outputFile,
|
|
935
|
+
forceTarget: parseTargetFlag(argv),
|
|
787
936
|
};
|
|
788
937
|
}
|
|
789
938
|
|
|
790
|
-
|
|
791
|
-
|
|
939
|
+
// Compiles once and writes the output when an output file is configured.
|
|
940
|
+
// Returns the build info; throws on compile errors.
|
|
941
|
+
function runDevBuild(
|
|
942
|
+
{
|
|
943
|
+
srcDir,
|
|
944
|
+
isDir,
|
|
945
|
+
outputFile,
|
|
946
|
+
sourceMap,
|
|
947
|
+
minify,
|
|
948
|
+
mangle,
|
|
949
|
+
forceTarget,
|
|
950
|
+
targetOverrides,
|
|
951
|
+
rootDir,
|
|
952
|
+
},
|
|
792
953
|
changedFile = null,
|
|
793
954
|
) {
|
|
955
|
+
const startMs = performance.now();
|
|
956
|
+
const result = runCompile(srcDir, isDir, {
|
|
957
|
+
outputFile,
|
|
958
|
+
sourceMap,
|
|
959
|
+
forceTarget,
|
|
960
|
+
targetOverrides,
|
|
961
|
+
rootDir,
|
|
962
|
+
});
|
|
963
|
+
const js = maybeMinify(result.js, { minify, mangle, sourceMap });
|
|
964
|
+
const written = outputFile
|
|
965
|
+
? writeCompileOutput(outputFile, result, js)
|
|
966
|
+
: null;
|
|
967
|
+
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
968
|
+
return { result, js, written, outputFile, elapsedMs, changedFile };
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function reportDevBuild({ written, outputFile, elapsedMs, changedFile }) {
|
|
972
|
+
const note = changedFile ? ` — ${changedFile} changed` : "";
|
|
973
|
+
log.ok(
|
|
974
|
+
`— wrote ${formatWrittenDesc(written, outputFile)} ${colors.dim(`(${elapsedMs}ms${note})`)}`,
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function buildDevOnce(config, changedFile = null) {
|
|
794
979
|
try {
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
sourceMap: sourceMap ?? true,
|
|
799
|
-
});
|
|
800
|
-
const written = writeCompileOutput(outputFile, result);
|
|
801
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
802
|
-
const note = changedFile ? ` — ${changedFile} changed` : "";
|
|
803
|
-
log.ok(
|
|
804
|
-
`— wrote ${formatWrittenDesc(written, outputFile)} ${colors.dim(`(${elapsedMs}ms${note})`)}`,
|
|
805
|
-
);
|
|
806
|
-
devServer.notify();
|
|
980
|
+
const info = runDevBuild(config, changedFile);
|
|
981
|
+
(config.onBuild ?? reportDevBuild)(info);
|
|
982
|
+
config.devServer?.notify();
|
|
807
983
|
} catch (e) {
|
|
808
|
-
|
|
809
|
-
|
|
984
|
+
if (config.onError) config.onError(e);
|
|
985
|
+
else log.error(e.message);
|
|
986
|
+
config.devServer?.notifyError(e);
|
|
810
987
|
}
|
|
811
988
|
}
|
|
812
989
|
|
|
@@ -849,10 +1026,12 @@ function setupDevWatchers(config) {
|
|
|
849
1026
|
let htmlDebounce = null;
|
|
850
1027
|
|
|
851
1028
|
const handleCss = (filename) => {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1029
|
+
if (config.copyAssets) {
|
|
1030
|
+
try {
|
|
1031
|
+
copyAssets(config.project.projectRoot, config.assetConfig);
|
|
1032
|
+
} catch {}
|
|
1033
|
+
}
|
|
1034
|
+
config.devServer?.notifyCss(filename);
|
|
856
1035
|
};
|
|
857
1036
|
|
|
858
1037
|
const watchers = [];
|
|
@@ -869,22 +1048,24 @@ function setupDevWatchers(config) {
|
|
|
869
1048
|
);
|
|
870
1049
|
if (srcWatcher) watchers.push(srcWatcher);
|
|
871
1050
|
|
|
872
|
-
const serveWatcher =
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1051
|
+
const serveWatcher = config.devServer
|
|
1052
|
+
? createServeWatcher(
|
|
1053
|
+
config.serveDir,
|
|
1054
|
+
config.watchTarget,
|
|
1055
|
+
(fn) => {
|
|
1056
|
+
clearTimeout(cssDebounce);
|
|
1057
|
+
cssDebounce = setTimeout(() => handleCss(fn), 50);
|
|
1058
|
+
},
|
|
1059
|
+
() => {
|
|
1060
|
+
clearTimeout(htmlDebounce);
|
|
1061
|
+
// Typed reload keeps a pending build error visible after the page reloads.
|
|
1062
|
+
htmlDebounce = setTimeout(
|
|
1063
|
+
() => config.devServer.notify({ type: "reload" }),
|
|
1064
|
+
50,
|
|
1065
|
+
);
|
|
1066
|
+
},
|
|
1067
|
+
)
|
|
1068
|
+
: null;
|
|
888
1069
|
if (serveWatcher) watchers.push(serveWatcher);
|
|
889
1070
|
|
|
890
1071
|
return {
|
|
@@ -902,6 +1083,12 @@ function setupDevWatchers(config) {
|
|
|
902
1083
|
};
|
|
903
1084
|
}
|
|
904
1085
|
|
|
1086
|
+
// Options beyond the `dev` subcommand's own (used by the legacy `--watch` path):
|
|
1087
|
+
// serve: false → no dev server (watch + rebuild only)
|
|
1088
|
+
// outputFile: null → never write; the compiled JS is passed to `onBuild`
|
|
1089
|
+
// copyAssets: false → skip the initial and per-CSS-change asset copy
|
|
1090
|
+
// minify / mangle → applied to every build
|
|
1091
|
+
// onBuild / onError → replace the default log lines
|
|
905
1092
|
export async function handleDev(targetDir = ".", options = {}) {
|
|
906
1093
|
const project = detectProject(targetDir);
|
|
907
1094
|
const port = options.port ?? project.port ?? 3000;
|
|
@@ -909,43 +1096,59 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
909
1096
|
? resolve(options.serveDir)
|
|
910
1097
|
: project.serveDir;
|
|
911
1098
|
const srcDir = options.srcDir ? resolve(options.srcDir) : project.srcDir;
|
|
912
|
-
const outputFile =
|
|
913
|
-
|
|
914
|
-
|
|
1099
|
+
const outputFile =
|
|
1100
|
+
options.outputFile === null
|
|
1101
|
+
? null
|
|
1102
|
+
: options.outputFile
|
|
1103
|
+
? resolve(options.outputFile)
|
|
1104
|
+
: project.devOutputFile;
|
|
1105
|
+
const doCopyAssets = options.copyAssets ?? true;
|
|
915
1106
|
|
|
916
|
-
copyAssets(project.projectRoot, options.assetConfig);
|
|
1107
|
+
if (doCopyAssets) copyAssets(project.projectRoot, options.assetConfig);
|
|
917
1108
|
|
|
918
1109
|
const isDir = statSync(srcDir).isDirectory();
|
|
919
|
-
const devServer =
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1110
|
+
const devServer =
|
|
1111
|
+
options.serve === false
|
|
1112
|
+
? null
|
|
1113
|
+
: createDevServer(serveDir, port, {
|
|
1114
|
+
...options,
|
|
1115
|
+
silent: options.silent ?? true,
|
|
1116
|
+
});
|
|
1117
|
+
const buildConfig = {
|
|
1118
|
+
srcDir,
|
|
1119
|
+
isDir,
|
|
1120
|
+
outputFile,
|
|
1121
|
+
sourceMap: options.sourceMap ?? true,
|
|
1122
|
+
minify: options.minify ?? false,
|
|
1123
|
+
mangle: options.mangle ?? false,
|
|
1124
|
+
forceTarget: options.forceTarget ?? null,
|
|
1125
|
+
...projectTargetOptions(project, options),
|
|
1126
|
+
devServer,
|
|
1127
|
+
onBuild: options.onBuild,
|
|
1128
|
+
onError: options.onError,
|
|
1129
|
+
};
|
|
923
1130
|
let initialError = null;
|
|
924
1131
|
|
|
925
1132
|
let written = null;
|
|
926
1133
|
try {
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
});
|
|
931
|
-
written = writeCompileOutput(outputFile, result);
|
|
1134
|
+
const info = runDevBuild(buildConfig);
|
|
1135
|
+
written = info.written;
|
|
1136
|
+
options.onBuild?.(info);
|
|
932
1137
|
} catch (err) {
|
|
933
1138
|
initialError = err;
|
|
934
|
-
|
|
1139
|
+
options.onError?.(err);
|
|
1140
|
+
devServer?.notifyError(err);
|
|
935
1141
|
}
|
|
936
1142
|
|
|
937
1143
|
let watcherController = null;
|
|
938
1144
|
if (options.watch !== false) {
|
|
939
1145
|
const watchTarget = isDir ? srcDir : dirname(srcDir);
|
|
940
1146
|
watcherController = setupDevWatchers({
|
|
1147
|
+
...buildConfig,
|
|
941
1148
|
watchTarget,
|
|
942
|
-
srcDir,
|
|
943
|
-
isDir,
|
|
944
|
-
outputFile,
|
|
945
|
-
sourceMap: options.sourceMap,
|
|
946
|
-
devServer,
|
|
947
1149
|
project,
|
|
948
1150
|
serveDir,
|
|
1151
|
+
copyAssets: doCopyAssets,
|
|
949
1152
|
assetConfig: options.assetConfig,
|
|
950
1153
|
});
|
|
951
1154
|
}
|
|
@@ -961,7 +1164,7 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
961
1164
|
written,
|
|
962
1165
|
close: async () => {
|
|
963
1166
|
watcherController?.close();
|
|
964
|
-
await devServer
|
|
1167
|
+
await devServer?.close();
|
|
965
1168
|
},
|
|
966
1169
|
};
|
|
967
1170
|
}
|