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/compiler.js
CHANGED
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
// de-qualifies it because the dependency is inlined.
|
|
19
19
|
|
|
20
20
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
22
|
-
import {
|
|
23
|
-
|
|
21
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
22
|
+
import {
|
|
23
|
+
buildSourceMap,
|
|
24
|
+
buildWasmSourceMap,
|
|
25
|
+
CodeGen,
|
|
26
|
+
} from "./backend/js/index.js";
|
|
27
|
+
import { compileWasmModule, optimizeWasm } from "./backend/wasm/index.js";
|
|
24
28
|
import { log } from "./colors.js";
|
|
25
29
|
import { parseDts } from "./dts-parser.js";
|
|
26
30
|
import { Lexer } from "./lexer.js";
|
|
@@ -67,6 +71,9 @@ function parseGoFrontFile(filePath) {
|
|
|
67
71
|
? new TemplParser(tokens, filename, source).parse()
|
|
68
72
|
: new Parser(tokens, filename, source).parse();
|
|
69
73
|
ast._source = source;
|
|
74
|
+
ast._path = resolve(filePath);
|
|
75
|
+
// compileFiles overwrites `target` with the resolved one; keep the directive.
|
|
76
|
+
ast._declaredTarget = ast.target;
|
|
70
77
|
_parseCache.set(filePath, { mtime, ast });
|
|
71
78
|
return ast;
|
|
72
79
|
}
|
|
@@ -163,6 +170,48 @@ function attachSourceMap(merged, outputDir) {
|
|
|
163
170
|
merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
const WASM_MAP_URL = "app.wasm.map";
|
|
174
|
+
|
|
175
|
+
// Function-level `app.wasm.map` (sources relative to the output dir).
|
|
176
|
+
function buildWasmMap(info, outputDir) {
|
|
177
|
+
if (!info || info.mappings.length === 0) return null;
|
|
178
|
+
return buildWasmSourceMap(
|
|
179
|
+
info.sources.map((f) => relative(outputDir, f)),
|
|
180
|
+
info.mappings,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Shared tail of compileFiles / compileSingleFile: link `app.wasm`, run
|
|
185
|
+
// wasm-opt when requested and produce the wasm source map.
|
|
186
|
+
function finishWasm(options, merged, pkgTarget, outputDir) {
|
|
187
|
+
const wantMap = Boolean(options.sourceMap && !options.isDependency);
|
|
188
|
+
let { wasm, wat, sourceMapInfo } = linkIntoBundle(
|
|
189
|
+
options,
|
|
190
|
+
merged,
|
|
191
|
+
pkgTarget,
|
|
192
|
+
{
|
|
193
|
+
sourceMappingURL: wantMap ? WASM_MAP_URL : null,
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
let wasmOptInfo = null;
|
|
197
|
+
let wasmSourceMap =
|
|
198
|
+
wantMap && wasm ? buildWasmMap(sourceMapInfo, outputDir) : null;
|
|
199
|
+
|
|
200
|
+
if (wasm && options.wasmOpt) {
|
|
201
|
+
const opt = optimizeWasm(wasm, {
|
|
202
|
+
sourceMap: Boolean(wasmSourceMap),
|
|
203
|
+
inputSourceMap: wasmSourceMap,
|
|
204
|
+
sourceMapUrl: WASM_MAP_URL,
|
|
205
|
+
emitWat: Boolean(options.emitWat),
|
|
206
|
+
});
|
|
207
|
+
wasm = opt.wasm;
|
|
208
|
+
if (opt.wat) wat = opt.wat;
|
|
209
|
+
wasmOptInfo = opt;
|
|
210
|
+
if (opt.sourceMap) wasmSourceMap = opt.sourceMap;
|
|
211
|
+
}
|
|
212
|
+
return { wasm, wat, wasmOptInfo, wasmSourceMap };
|
|
213
|
+
}
|
|
214
|
+
|
|
166
215
|
// ── WASM linking ──────────────────────────────────────────────
|
|
167
216
|
//
|
|
168
217
|
// Every `wasm`/`both` package reached during a build registers a unit in the
|
|
@@ -205,7 +254,10 @@ function registerPackageUnit(options, unit) {
|
|
|
205
254
|
return emitTarget;
|
|
206
255
|
}
|
|
207
256
|
|
|
208
|
-
function linkWasmUnits(
|
|
257
|
+
function linkWasmUnits(
|
|
258
|
+
units,
|
|
259
|
+
{ emitWat = false, rootTarget = "js", sourceMappingURL = null } = {},
|
|
260
|
+
) {
|
|
209
261
|
if (!units.some((u) => u.target === "wasm")) return null;
|
|
210
262
|
checkLinkCollisions(units);
|
|
211
263
|
|
|
@@ -224,6 +276,7 @@ function linkWasmUnits(units, { emitWat = false, rootTarget = "js" } = {}) {
|
|
|
224
276
|
bundledPackages,
|
|
225
277
|
emitWat,
|
|
226
278
|
callMain: rootTarget === "wasm",
|
|
279
|
+
sourceMappingURL,
|
|
227
280
|
});
|
|
228
281
|
}
|
|
229
282
|
|
|
@@ -291,17 +344,23 @@ function spliceFacade(merged, facade) {
|
|
|
291
344
|
}
|
|
292
345
|
|
|
293
346
|
// Root compiles only: link every registered unit into app.wasm and splice the
|
|
294
|
-
// facade into the merged bundle. Returns `{ wasm, wat }`
|
|
295
|
-
// is nothing to link).
|
|
296
|
-
function linkIntoBundle(options, merged, pkgTarget) {
|
|
297
|
-
|
|
347
|
+
// facade into the merged bundle. Returns `{ wasm, wat, sourceMapInfo }`
|
|
348
|
+
// (nulls when there is nothing to link).
|
|
349
|
+
function linkIntoBundle(options, merged, pkgTarget, { sourceMappingURL } = {}) {
|
|
350
|
+
const none = { wasm: null, wat: null, sourceMapInfo: null };
|
|
351
|
+
if (options.isDependency) return none;
|
|
298
352
|
const linked = linkWasmUnits(options.wasmUnits, {
|
|
299
353
|
emitWat: options.emitWat,
|
|
300
354
|
rootTarget: pkgTarget,
|
|
355
|
+
sourceMappingURL,
|
|
301
356
|
});
|
|
302
|
-
if (!linked) return
|
|
357
|
+
if (!linked) return none;
|
|
303
358
|
spliceFacade(merged, linked.facade);
|
|
304
|
-
return {
|
|
359
|
+
return {
|
|
360
|
+
wasm: linked.wasm,
|
|
361
|
+
wat: linked.wat ?? null,
|
|
362
|
+
sourceMapInfo: linked.sourceMapInfo ?? null,
|
|
363
|
+
};
|
|
305
364
|
}
|
|
306
365
|
|
|
307
366
|
// ── Import resolution ─────────────────────────────────────────
|
|
@@ -309,7 +368,7 @@ function linkIntoBundle(options, merged, pkgTarget) {
|
|
|
309
368
|
// Shared by compileFiles (multi-file) and the single-file path in index.js.
|
|
310
369
|
// Mutates checker, jsImports, bundledPackages, and preambles in place.
|
|
311
370
|
|
|
312
|
-
|
|
371
|
+
function resolveImports(
|
|
313
372
|
programs,
|
|
314
373
|
fromFile,
|
|
315
374
|
checker,
|
|
@@ -350,6 +409,11 @@ export function resolveImports(
|
|
|
350
409
|
"package 'gom' is not available in wasm packages",
|
|
351
410
|
impNode,
|
|
352
411
|
);
|
|
412
|
+
} else if (path === "gofront/shared" && pkgTarget === "both") {
|
|
413
|
+
checker.err(
|
|
414
|
+
"'gofront/shared' requires a wasm-only package (//gofront:target wasm): a both package has no linear memory on the JS side",
|
|
415
|
+
impNode,
|
|
416
|
+
);
|
|
353
417
|
} else if (!isLocalPath(path)) {
|
|
354
418
|
if (!isBuiltinPackage(path)) {
|
|
355
419
|
checker.recordBlocker("js: import", path);
|
|
@@ -472,6 +536,7 @@ export function resolveImports(
|
|
|
472
536
|
nameUsed,
|
|
473
537
|
dep.exportedSymbols,
|
|
474
538
|
dep.exportedTypes,
|
|
539
|
+
dep.target,
|
|
475
540
|
);
|
|
476
541
|
checker.trackImport(nameUsed, { _line }, p._filename, p._source);
|
|
477
542
|
}
|
|
@@ -481,6 +546,29 @@ export function resolveImports(
|
|
|
481
546
|
|
|
482
547
|
// ── Main entry points ─────────────────────────────────────────
|
|
483
548
|
|
|
549
|
+
// Package target after build-time overrides (Task H6.3): `forceTarget`
|
|
550
|
+
// (`--js-only`) compiles every package to JS; `targetOverrides` maps package
|
|
551
|
+
// directories relative to the root source dir (`"engine/physics"`, `"."` for
|
|
552
|
+
// the root) to a target, replacing the `//gofront:target` directive.
|
|
553
|
+
const PACKAGE_TARGETS = new Set(["js", "wasm", "both"]);
|
|
554
|
+
|
|
555
|
+
export function resolvePackageTarget(declared, pkgDir, options = {}) {
|
|
556
|
+
if (options.forceTarget) return options.forceTarget;
|
|
557
|
+
const overrides = options.targetOverrides;
|
|
558
|
+
if (!overrides || !options.rootDir) return declared;
|
|
559
|
+
const rel = relative(resolve(options.rootDir), resolve(pkgDir))
|
|
560
|
+
.split(sep)
|
|
561
|
+
.join("/");
|
|
562
|
+
const key = rel === "" ? "." : rel;
|
|
563
|
+
const t = overrides[key] ?? (key === "." ? overrides[""] : undefined);
|
|
564
|
+
if (t === undefined) return declared;
|
|
565
|
+
if (!PACKAGE_TARGETS.has(t))
|
|
566
|
+
throw new Error(
|
|
567
|
+
`gofront.json: invalid target '${t}' for package '${key}' (expected js, wasm or both)`,
|
|
568
|
+
);
|
|
569
|
+
return t;
|
|
570
|
+
}
|
|
571
|
+
|
|
484
572
|
export function compileSingleFile(inputPath, options = {}) {
|
|
485
573
|
const {
|
|
486
574
|
sourceMap = false,
|
|
@@ -502,6 +590,7 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
502
590
|
|
|
503
591
|
const ast = new Parser(tokens, basename(inputPath), source).parse();
|
|
504
592
|
ast._source = source;
|
|
593
|
+
ast._path = resolve(inputPath);
|
|
505
594
|
if (dumpAst) return { ast };
|
|
506
595
|
|
|
507
596
|
const outputDir =
|
|
@@ -513,7 +602,11 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
513
602
|
const bundledPackages = new Set();
|
|
514
603
|
const preambles = [];
|
|
515
604
|
|
|
516
|
-
const pkgTarget =
|
|
605
|
+
const pkgTarget = resolvePackageTarget(
|
|
606
|
+
ast.target ?? options.target ?? "js",
|
|
607
|
+
dirname(resolve(inputPath)),
|
|
608
|
+
options,
|
|
609
|
+
);
|
|
517
610
|
ast.target = pkgTarget;
|
|
518
611
|
checker.target = pkgTarget;
|
|
519
612
|
checker.pkgName = ast.pkg?.name ?? "main";
|
|
@@ -534,6 +627,7 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
534
627
|
if (errors.length > 0) {
|
|
535
628
|
throw new Error(errors.map((e) => e.message).join("\n"));
|
|
536
629
|
}
|
|
630
|
+
for (const w of checker.warnings) log.warn(w);
|
|
537
631
|
|
|
538
632
|
if (pkgTarget === "wasm" || pkgTarget === "both") {
|
|
539
633
|
registerWasmUnit(options, {
|
|
@@ -562,13 +656,26 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
562
656
|
};
|
|
563
657
|
|
|
564
658
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
565
|
-
const { wasm, wat } =
|
|
659
|
+
const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
|
|
660
|
+
{ ...options, sourceMap },
|
|
661
|
+
merged,
|
|
662
|
+
pkgTarget,
|
|
663
|
+
outputDir,
|
|
664
|
+
);
|
|
566
665
|
|
|
567
666
|
if (sourceMap && !options.isDependency) {
|
|
568
667
|
attachSourceMap(merged, outputDir);
|
|
569
668
|
}
|
|
570
669
|
|
|
571
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
js: merged.js,
|
|
672
|
+
css: cg.getCss(),
|
|
673
|
+
target: pkgTarget,
|
|
674
|
+
wasm,
|
|
675
|
+
wat,
|
|
676
|
+
wasmOptInfo,
|
|
677
|
+
wasmSourceMap,
|
|
678
|
+
};
|
|
572
679
|
}
|
|
573
680
|
|
|
574
681
|
export function compileDir(dir, options = {}) {
|
|
@@ -591,8 +698,12 @@ export function compilePackageTestsWasm(dir, options = {}) {
|
|
|
591
698
|
}
|
|
592
699
|
|
|
593
700
|
export function compileFiles(files, options = {}) {
|
|
594
|
-
options = { ...options, wasmUnits: options.wasmUnits ?? [] };
|
|
595
701
|
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
702
|
+
options = {
|
|
703
|
+
...options,
|
|
704
|
+
wasmUnits: options.wasmUnits ?? [],
|
|
705
|
+
rootDir: options.rootDir ?? fromDir,
|
|
706
|
+
};
|
|
596
707
|
const outputDir =
|
|
597
708
|
options.outputDir ??
|
|
598
709
|
(options.outputFile ? dirname(resolve(options.outputFile)) : fromDir);
|
|
@@ -622,9 +733,10 @@ export function compileFiles(files, options = {}) {
|
|
|
622
733
|
// Validate target directives across files in this package
|
|
623
734
|
const declaredTargets = new Map();
|
|
624
735
|
for (const p of programs) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
declaredTargets.
|
|
736
|
+
const declared = p._declaredTarget;
|
|
737
|
+
if (declared) {
|
|
738
|
+
if (!declaredTargets.has(declared)) declaredTargets.set(declared, []);
|
|
739
|
+
declaredTargets.get(declared).push(p._filename);
|
|
628
740
|
}
|
|
629
741
|
}
|
|
630
742
|
if (declaredTargets.size > 1) {
|
|
@@ -635,10 +747,13 @@ export function compileFiles(files, options = {}) {
|
|
|
635
747
|
`Conflicting //gofront:target directives in package '${pkgName}': ${list}`,
|
|
636
748
|
);
|
|
637
749
|
}
|
|
638
|
-
const pkgTarget =
|
|
750
|
+
const pkgTarget = resolvePackageTarget(
|
|
639
751
|
declaredTargets.size === 1
|
|
640
752
|
? declaredTargets.keys().next().value
|
|
641
|
-
: (options.target ?? "js")
|
|
753
|
+
: (options.target ?? "js"),
|
|
754
|
+
fromDir,
|
|
755
|
+
options,
|
|
756
|
+
);
|
|
642
757
|
for (const p of programs) p.target = pkgTarget;
|
|
643
758
|
|
|
644
759
|
// ── 2. Resolve imports ────────────────────────────────────────
|
|
@@ -668,6 +783,7 @@ export function compileFiles(files, options = {}) {
|
|
|
668
783
|
const msgs = errors.map((e) => e.message).join("\n");
|
|
669
784
|
throw new Error(msgs);
|
|
670
785
|
}
|
|
786
|
+
for (const w of checker.warnings) log.warn(w);
|
|
671
787
|
|
|
672
788
|
// ── 4. Code generation ────────────────────────────────────────
|
|
673
789
|
const emitTarget = registerPackageUnit(options, {
|
|
@@ -699,7 +815,12 @@ export function compileFiles(files, options = {}) {
|
|
|
699
815
|
};
|
|
700
816
|
|
|
701
817
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
702
|
-
const { wasm, wat } =
|
|
818
|
+
const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
|
|
819
|
+
options,
|
|
820
|
+
merged,
|
|
821
|
+
pkgTarget,
|
|
822
|
+
outputDir,
|
|
823
|
+
);
|
|
703
824
|
|
|
704
825
|
if (options.sourceMap && !options.isDependency) {
|
|
705
826
|
attachSourceMap(merged, outputDir);
|
|
@@ -717,6 +838,8 @@ export function compileFiles(files, options = {}) {
|
|
|
717
838
|
css: allCss,
|
|
718
839
|
wasm,
|
|
719
840
|
wat,
|
|
841
|
+
wasmOptInfo,
|
|
842
|
+
wasmSourceMap,
|
|
720
843
|
programs,
|
|
721
844
|
exportedSymbols: checker.getExportedSymbols(),
|
|
722
845
|
exportedTypes: checker.getExportedTypes(),
|
package/src/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { createRequire } from "node:module";
|
|
|
18
18
|
const _require = createRequire(import.meta.url);
|
|
19
19
|
const { version } = _require("../package.json");
|
|
20
20
|
|
|
21
|
-
import { statSync
|
|
21
|
+
import { statSync } from "node:fs";
|
|
22
22
|
import { dirname, relative, resolve } from "node:path";
|
|
23
23
|
import { copyAssets } from "./asset-manager.js";
|
|
24
24
|
import {
|
|
@@ -35,13 +35,13 @@ import {
|
|
|
35
35
|
parseBuildArgs,
|
|
36
36
|
parseCheckArgs,
|
|
37
37
|
parseDevArgs,
|
|
38
|
+
parseLegacyArgs,
|
|
38
39
|
parsePrepArgs,
|
|
39
40
|
parseTestArgs,
|
|
40
41
|
runCompile,
|
|
41
42
|
writeCompileOutput,
|
|
42
43
|
} from "./cli-core.js";
|
|
43
44
|
import { colors, formatDiagnostic, log, ms, stamp } from "./colors.js";
|
|
44
|
-
import { createDevServer } from "./dev-server.js";
|
|
45
45
|
|
|
46
46
|
// ── Parse CLI args ───────────────────────────────────────────
|
|
47
47
|
|
|
@@ -54,11 +54,11 @@ if (args[0] === "--version" || args[0] === "-v") {
|
|
|
54
54
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
55
55
|
console.log(
|
|
56
56
|
`
|
|
57
|
-
GoFront — a Go-inspired language that compiles to JavaScript
|
|
57
|
+
GoFront — a Go-inspired language that compiles to JavaScript and WebAssembly
|
|
58
58
|
|
|
59
59
|
Usage:
|
|
60
60
|
gofront dev [dir] [options] Start dev server with live reload (default port 3000)
|
|
61
|
-
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat)
|
|
61
|
+
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat, --release, --wasm-opt, --js-only)
|
|
62
62
|
gofront check [dir|dir/...] Type-check only (dir/... recurses into every package)
|
|
63
63
|
gofront test [dir|dir/...] [--dom] Run unit tests (-v verbose, -run <regex>)
|
|
64
64
|
gofront prep [dir] [--minify] Copy static assets and bundle vendor dependencies
|
|
@@ -66,6 +66,8 @@ Usage:
|
|
|
66
66
|
gofront <file.go> Compile single file and print to stdout
|
|
67
67
|
gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
|
|
68
68
|
gofront <input> -o out.js Compile and write to file (wasm packages also emit app.wasm)
|
|
69
|
+
gofront <input> --release Production build with minification and Binaryen wasm optimization
|
|
70
|
+
gofront <input> --wasm-opt Optimize wasm output with Binaryen (-O3 + GUFA)
|
|
69
71
|
gofront <input> --check Type-check only
|
|
70
72
|
gofront <input> --watch Watch for changes and recompile
|
|
71
73
|
gofront <input> -o out.js --serve Watch + serve with live reload (default port 3000)
|
|
@@ -131,9 +133,9 @@ if (args[0] === "test") {
|
|
|
131
133
|
// ── check subcommand ──────────────────────────────────────────
|
|
132
134
|
|
|
133
135
|
if (args[0] === "check") {
|
|
134
|
-
const { targetDir } = parseCheckArgs(args.slice(1));
|
|
136
|
+
const { targetDir, forceTarget } = parseCheckArgs(args.slice(1));
|
|
135
137
|
try {
|
|
136
|
-
const { elapsedMs, packages } = handleCheck(targetDir);
|
|
138
|
+
const { elapsedMs, packages } = handleCheck(targetDir, { forceTarget });
|
|
137
139
|
const okLine = (label, t) =>
|
|
138
140
|
log.info(`${colors.cyan(label)} — ${colors.green("OK")} ${ms(t)}`);
|
|
139
141
|
if (packages) {
|
|
@@ -202,20 +204,22 @@ if (args[0] === "dev") {
|
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
const {
|
|
208
|
+
inputArg,
|
|
209
|
+
outputFile,
|
|
210
|
+
checkOnly,
|
|
211
|
+
dumpAst,
|
|
212
|
+
dumpTokens,
|
|
213
|
+
sourceMap,
|
|
214
|
+
serve: serveMode,
|
|
215
|
+
watch: watchMode,
|
|
216
|
+
copyAssets: copyAssetsFlag,
|
|
217
|
+
wasmOpt,
|
|
218
|
+
minify: minifyOutput,
|
|
219
|
+
mangle: mangleOutput,
|
|
220
|
+
port: servePort,
|
|
221
|
+
forceTarget,
|
|
222
|
+
} = parseLegacyArgs(args);
|
|
219
223
|
|
|
220
224
|
// ── Determine input mode ─────────────────────────────────────
|
|
221
225
|
|
|
@@ -228,6 +232,17 @@ try {
|
|
|
228
232
|
process.exit(1);
|
|
229
233
|
}
|
|
230
234
|
|
|
235
|
+
function copyProjectAssets(verbose) {
|
|
236
|
+
try {
|
|
237
|
+
const { copied, skipped } = copyAssets(resolve("."));
|
|
238
|
+
if (verbose && (copied > 0 || skipped > 0)) {
|
|
239
|
+
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
log.warn(`asset copy failed: ${e.message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
231
246
|
// ── Single-shot mode ─────────────────────────────────────────
|
|
232
247
|
|
|
233
248
|
if (!watchMode) {
|
|
@@ -239,6 +254,8 @@ if (!watchMode) {
|
|
|
239
254
|
outputFile,
|
|
240
255
|
dumpTokens,
|
|
241
256
|
dumpAst,
|
|
257
|
+
wasmOpt,
|
|
258
|
+
forceTarget,
|
|
242
259
|
});
|
|
243
260
|
} catch (e) {
|
|
244
261
|
log.fail(e.message);
|
|
@@ -282,6 +299,13 @@ if (!watchMode) {
|
|
|
282
299
|
log.info(
|
|
283
300
|
`wrote ${formatWrittenDesc(written, outputFile)} ${ms(elapsedMs)}`,
|
|
284
301
|
);
|
|
302
|
+
if (result.wasmOptInfo && result.wasmOptInfo.originalSize > 0) {
|
|
303
|
+
const origKb = (result.wasmOptInfo.originalSize / 1024).toFixed(1);
|
|
304
|
+
const optKb = (result.wasmOptInfo.optimizedSize / 1024).toFixed(1);
|
|
305
|
+
log.info(
|
|
306
|
+
`wasm-opt: app.wasm ${origKb} kB → ${optKb} kB (-${result.wasmOptInfo.percentSaved}%) [${result.wasmOptInfo.engine}]`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
285
309
|
} catch (e) {
|
|
286
310
|
log.fail(`cannot write '${outputFile}': ${e.message}`);
|
|
287
311
|
process.exit(1);
|
|
@@ -295,110 +319,57 @@ if (!watchMode) {
|
|
|
295
319
|
}
|
|
296
320
|
}
|
|
297
321
|
|
|
298
|
-
if (copyAssetsFlag)
|
|
299
|
-
try {
|
|
300
|
-
const projectDir = resolve(".");
|
|
301
|
-
const { copied, skipped } = copyAssets(projectDir);
|
|
302
|
-
if (copied > 0 || skipped > 0) {
|
|
303
|
-
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
304
|
-
}
|
|
305
|
-
} catch (e) {
|
|
306
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
322
|
+
if (copyAssetsFlag) copyProjectAssets(true);
|
|
309
323
|
|
|
310
324
|
process.exit(0);
|
|
311
325
|
}
|
|
312
326
|
|
|
313
327
|
// ── Watch mode ───────────────────────────────────────────────
|
|
314
328
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!outputFile) {
|
|
319
|
-
log.fail("--serve requires -o <output file>");
|
|
320
|
-
process.exit(1);
|
|
321
|
-
}
|
|
322
|
-
const serveDir = dirname(resolve(outputFile));
|
|
323
|
-
devServer = createDevServer(serveDir, servePort);
|
|
329
|
+
if (serveMode && !outputFile) {
|
|
330
|
+
log.fail("--serve requires -o <output file>");
|
|
331
|
+
process.exit(1);
|
|
324
332
|
}
|
|
325
333
|
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const startMs = performance.now();
|
|
329
|
-
const result = runCompile(inputPath, isDir, { sourceMap, outputFile });
|
|
330
|
-
const js = maybeMinify(result.js, {
|
|
331
|
-
minify: minifyOutput,
|
|
332
|
-
mangle: mangleOutput,
|
|
333
|
-
sourceMap,
|
|
334
|
-
});
|
|
335
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
336
|
-
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
337
|
-
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
338
|
-
if (outputFile) {
|
|
339
|
-
const written = writeCompileOutput(resolve(outputFile), result, js);
|
|
340
|
-
console.error(
|
|
341
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
342
|
-
);
|
|
343
|
-
} else {
|
|
344
|
-
// Clear screen then print
|
|
345
|
-
process.stdout.write("\x1Bc");
|
|
346
|
-
console.log(js);
|
|
347
|
-
console.error(
|
|
348
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} ${timing}`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
334
|
+
const prefix = () => `${stamp()} ${colors.bold("gofront:")}`;
|
|
351
335
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
336
|
+
try {
|
|
337
|
+
await handleDev(".", {
|
|
338
|
+
srcDir: inputPath,
|
|
339
|
+
outputFile,
|
|
340
|
+
serve: serveMode,
|
|
341
|
+
serveDir: outputFile ? dirname(resolve(outputFile)) : undefined,
|
|
342
|
+
port: servePort,
|
|
343
|
+
silent: false,
|
|
344
|
+
sourceMap,
|
|
345
|
+
minify: minifyOutput,
|
|
346
|
+
mangle: mangleOutput,
|
|
347
|
+
copyAssets: copyAssetsFlag,
|
|
348
|
+
forceTarget,
|
|
349
|
+
onBuild: ({ js, written, elapsedMs, changedFile }) => {
|
|
350
|
+
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
351
|
+
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
352
|
+
if (outputFile) {
|
|
353
|
+
console.error(
|
|
354
|
+
`${prefix()} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
355
|
+
);
|
|
356
|
+
} else {
|
|
357
|
+
// Clear screen then print
|
|
358
|
+
process.stdout.write("\x1Bc");
|
|
359
|
+
console.log(js);
|
|
360
|
+
console.error(`${prefix()} ${colors.green("OK")} ${timing}`);
|
|
357
361
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Initial build
|
|
372
|
-
buildOnce();
|
|
373
|
-
|
|
374
|
-
// Determine what to watch
|
|
375
|
-
const watchTarget = isDir ? inputPath : dirname(inputPath);
|
|
376
|
-
|
|
377
|
-
function handleCssWatch(filename) {
|
|
378
|
-
if (copyAssetsFlag) {
|
|
379
|
-
try {
|
|
380
|
-
copyAssets(resolve("."));
|
|
381
|
-
} catch (e) {
|
|
382
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
devServer?.notifyCss?.(filename);
|
|
362
|
+
if (copyAssetsFlag) copyProjectAssets(false);
|
|
363
|
+
},
|
|
364
|
+
onError: (e) => {
|
|
365
|
+
console.error(`${prefix()} ${colors.bold(colors.red("ERROR"))}`);
|
|
366
|
+
for (const line of formatDiagnostic(e.message).split("\n"))
|
|
367
|
+
console.error(` ${line}`);
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
} catch (e) {
|
|
371
|
+
log.fail(e.message);
|
|
372
|
+
process.exit(1);
|
|
386
373
|
}
|
|
387
374
|
|
|
388
|
-
|
|
389
|
-
let cssDebounce = null;
|
|
390
|
-
watch(watchTarget, { recursive: true }, (_event, filename) => {
|
|
391
|
-
if (!filename) return;
|
|
392
|
-
if (filename.endsWith(".css")) {
|
|
393
|
-
clearTimeout(cssDebounce);
|
|
394
|
-
cssDebounce = setTimeout(() => handleCssWatch(filename), 50);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!filename.endsWith(".go") && !filename.endsWith(".templ")) return;
|
|
398
|
-
clearTimeout(debounce);
|
|
399
|
-
debounce = setTimeout(() => buildOnce(filename), 80);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
console.error(
|
|
403
|
-
`${stamp()} ${colors.bold("gofront:")} watching ${colors.cyan(inputArg)} ...`,
|
|
404
|
-
);
|
|
375
|
+
console.error(`${prefix()} watching ${colors.cyan(inputArg)} ...`);
|
package/src/lower/boxing.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/lower/boxing.js
|
|
2
2
|
// Identifies address-taken scalar variables that require heap boxing ({ value: x }).
|
|
3
3
|
|
|
4
|
+
import { forEachChild } from "./walk.js";
|
|
5
|
+
|
|
4
6
|
export function isReferenceType(t) {
|
|
5
7
|
if (!t) return false;
|
|
6
8
|
const base = t.kind === "named" ? t.underlying : t;
|
|
@@ -28,9 +30,6 @@ export function scanAddressTaken(node, boxedVars = new Set()) {
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
// Recurse into FuncLit too — closures may take address of outer vars
|
|
31
|
-
|
|
32
|
-
if (key.startsWith("_")) continue;
|
|
33
|
-
scanAddressTaken(node[key], boxedVars);
|
|
34
|
-
}
|
|
33
|
+
forEachChild(node, (child) => scanAddressTaken(child, boxedVars));
|
|
35
34
|
return boxedVars;
|
|
36
35
|
}
|
package/src/lower/captures.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// are mutated and thus require heap-allocated environment cells for WASM closures.
|
|
4
4
|
|
|
5
5
|
import { collectMutatedVars, nodeMutatesVar } from "./ownership.js";
|
|
6
|
+
import { forEachChild } from "./walk.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Result of capture analysis on a function or closure.
|
|
@@ -41,17 +42,14 @@ export function collectDeclaredNames(node, names = new Set()) {
|
|
|
41
42
|
// Do not descend into nested functions when collecting local declarations
|
|
42
43
|
if (node.kind === "FuncLit") return names;
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
if (key.startsWith("_")) continue;
|
|
46
|
-
collectDeclaredNames(node[key], names);
|
|
47
|
-
}
|
|
45
|
+
forEachChild(node, (child) => collectDeclaredNames(child, names));
|
|
48
46
|
return names;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
/**
|
|
52
50
|
* Finds all identifier references in a node that are not shadowed by inner declarations.
|
|
53
51
|
*/
|
|
54
|
-
|
|
52
|
+
function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
55
53
|
if (!node || typeof node !== "object") return refs;
|
|
56
54
|
if (Array.isArray(node)) {
|
|
57
55
|
for (const item of node) collectReferencedNames(item, localDecls, refs);
|
|
@@ -73,10 +71,9 @@ export function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
|
73
71
|
if (r.name && r.name !== "_") innerLocals.add(r.name);
|
|
74
72
|
}
|
|
75
73
|
collectDeclaredNames(node.body, innerLocals);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
74
|
+
forEachChild(node, (child) =>
|
|
75
|
+
collectReferencedNames(child, innerLocals, refs),
|
|
76
|
+
);
|
|
80
77
|
return refs;
|
|
81
78
|
}
|
|
82
79
|
|
|
@@ -89,10 +86,9 @@ export function collectReferencedNames(node, localDecls, refs = new Set()) {
|
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
// For nested closures, free variables are also referenced
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
89
|
+
forEachChild(node, (child) =>
|
|
90
|
+
collectReferencedNames(child, localDecls, refs),
|
|
91
|
+
);
|
|
96
92
|
return refs;
|
|
97
93
|
}
|
|
98
94
|
|
|
@@ -168,10 +164,7 @@ export function analyzeCaptures(fnDecl) {
|
|
|
168
164
|
return;
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
|
|
172
|
-
if (key.startsWith("_")) continue;
|
|
173
|
-
walkClosures(node[key], scopeStack);
|
|
174
|
-
}
|
|
167
|
+
forEachChild(node, (child) => walkClosures(child, scopeStack));
|
|
175
168
|
};
|
|
176
169
|
|
|
177
170
|
walkClosures(fnDecl.body, [rootScope]);
|