gofront 1.4.0 → 1.5.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/README.md +72 -9
- package/package.json +2 -1
- package/src/{codegen → backend/js}/expressions.js +217 -60
- package/src/{codegen → backend/js}/index.js +21 -35
- package/src/{codegen → backend/js}/statements.js +43 -48
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/index.js +3 -0
- package/src/{codegen → backend/js}/templ.js +2 -2
- package/src/backend/wasm/boundary.js +1345 -0
- package/src/backend/wasm/emit.js +5779 -0
- package/src/backend/wasm/encode.js +789 -0
- package/src/backend/wasm/glue.js +130 -0
- package/src/backend/wasm/index.js +1413 -0
- package/src/backend/wasm/types.js +420 -0
- package/src/backend/wasm/wat.js +255 -0
- package/src/cli-core.js +52 -7
- package/src/compiler.js +322 -12
- package/src/dev-server.js +56 -15
- package/src/index.js +17 -9
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +36 -0
- package/src/lower/captures.js +179 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +169 -0
- package/src/lower/functions.js +30 -0
- package/src/lower/globals.js +324 -0
- package/src/lower/index.js +115 -0
- package/src/lower/ownership.js +142 -0
- package/src/lower/range.js +70 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +1 -0
- package/src/test-runner.js +74 -20
- package/src/typechecker/expressions.js +40 -1
- package/src/typechecker/index.js +234 -4
- package/src/typechecker/statements.js +7 -0
- package/src/typechecker/stdlib/core.js +17 -0
- package/src/typechecker/types.js +56 -2
- /package/src/{codegen → backend/js}/runtime.js +0 -0
- /package/src/{codegen → backend/js}/source-map.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/src/cli-core.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
sep,
|
|
24
24
|
} from "node:path";
|
|
25
25
|
import { copyAssets } from "./asset-manager.js";
|
|
26
|
+
import { isGoFrontWasm } from "./backend/wasm/index.js";
|
|
26
27
|
import { colors, log } from "./colors.js";
|
|
27
28
|
import { compileDir, compileSingleFile } from "./compiler.js";
|
|
28
29
|
import { createDevServer } from "./dev-server.js";
|
|
@@ -42,11 +43,12 @@ export function runCompile(inputPath, isDir, options) {
|
|
|
42
43
|
outputFile = null,
|
|
43
44
|
dumpTokens = false,
|
|
44
45
|
dumpAst = false,
|
|
46
|
+
emitWat = false,
|
|
45
47
|
} = options ?? {};
|
|
46
48
|
|
|
47
49
|
if (isDir) {
|
|
48
50
|
const outputDir = outputFile ? dirname(resolve(outputFile)) : resolve(".");
|
|
49
|
-
return compileDir(inputPath, { sourceMap, outputDir });
|
|
51
|
+
return compileDir(inputPath, { sourceMap, outputDir, emitWat });
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
return compileSingleFile(inputPath, {
|
|
@@ -54,9 +56,49 @@ export function runCompile(inputPath, isDir, options) {
|
|
|
54
56
|
outputFile,
|
|
55
57
|
dumpTokens,
|
|
56
58
|
dumpAst,
|
|
59
|
+
emitWat,
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
// Writes the JS bundle plus, when the project links wasm packages, the
|
|
64
|
+
// sibling `app.wasm` (and `app.wat` when requested). Stale wasm artifacts
|
|
65
|
+
// are removed so a project that drops its wasm packages serves clean output —
|
|
66
|
+
// but only when the existing file is one GoFront produced (recognised by its
|
|
67
|
+
// `gofront` custom section), so a hand-placed `app.wasm` is left alone.
|
|
68
|
+
export function writeCompileOutput(outputFile, result, js = result.js) {
|
|
69
|
+
mkdirSync(dirname(outputFile), { recursive: true });
|
|
70
|
+
writeFileSync(outputFile, `${js}\n`);
|
|
71
|
+
const wasmFile = join(dirname(outputFile), "app.wasm");
|
|
72
|
+
const watFile = join(dirname(outputFile), "app.wat");
|
|
73
|
+
const written = [outputFile];
|
|
74
|
+
let ownsWasm = Boolean(result.wasm);
|
|
75
|
+
if (result.wasm) {
|
|
76
|
+
writeFileSync(wasmFile, result.wasm);
|
|
77
|
+
written.push(wasmFile);
|
|
78
|
+
} else if (existsSync(wasmFile)) {
|
|
79
|
+
if (isGoFrontWasm(readFileSync(wasmFile))) {
|
|
80
|
+
rmSync(wasmFile);
|
|
81
|
+
ownsWasm = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (result.wat) {
|
|
85
|
+
writeFileSync(watFile, result.wat);
|
|
86
|
+
written.push(watFile);
|
|
87
|
+
} else if (ownsWasm && existsSync(watFile)) {
|
|
88
|
+
rmSync(watFile);
|
|
89
|
+
}
|
|
90
|
+
return written;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function formatWrittenDesc(written, outputFile = written?.[0] ?? "") {
|
|
94
|
+
if (!written || written.length <= 1) return colors.cyan(outputFile);
|
|
95
|
+
const extras = written
|
|
96
|
+
.slice(1)
|
|
97
|
+
.map((p) => colors.cyan(basename(p)))
|
|
98
|
+
.join(" + ");
|
|
99
|
+
return `${colors.cyan(outputFile)} + ${extras}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
export function maybeMinify(js, options) {
|
|
61
103
|
const {
|
|
62
104
|
minify: doMinify = false,
|
|
@@ -460,6 +502,7 @@ export function parseBuildArgs(argv) {
|
|
|
460
502
|
const sourceMap = argv.includes("--source-map");
|
|
461
503
|
const noMinify = argv.includes("--no-minify");
|
|
462
504
|
const noMangle = argv.includes("--no-mangle");
|
|
505
|
+
const emitWat = argv.includes("--emit-wat");
|
|
463
506
|
|
|
464
507
|
for (let i = 0; i < argv.length; i++) {
|
|
465
508
|
const arg = argv[i];
|
|
@@ -477,6 +520,7 @@ export function parseBuildArgs(argv) {
|
|
|
477
520
|
outDir,
|
|
478
521
|
pwa,
|
|
479
522
|
sourceMap,
|
|
523
|
+
emitWat,
|
|
480
524
|
minify: !noMinify,
|
|
481
525
|
mangle: !noMangle && !noMinify,
|
|
482
526
|
};
|
|
@@ -667,6 +711,7 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
667
711
|
const compileResult = runCompile(srcDir, isDir, {
|
|
668
712
|
outputFile,
|
|
669
713
|
sourceMap: options.sourceMap ?? false,
|
|
714
|
+
emitWat: options.emitWat ?? false,
|
|
670
715
|
});
|
|
671
716
|
|
|
672
717
|
const doMinify = options.minify ?? true;
|
|
@@ -676,7 +721,7 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
676
721
|
mangle: doMangle,
|
|
677
722
|
sourceMap: options.sourceMap ?? false,
|
|
678
723
|
});
|
|
679
|
-
|
|
724
|
+
writeCompileOutput(outputFile, compileResult, js);
|
|
680
725
|
|
|
681
726
|
let vendor = null;
|
|
682
727
|
try {
|
|
@@ -752,12 +797,11 @@ function buildDevOnce(
|
|
|
752
797
|
outputFile,
|
|
753
798
|
sourceMap: sourceMap ?? true,
|
|
754
799
|
});
|
|
755
|
-
|
|
756
|
-
writeFileSync(outputFile, `${result.js}\n`);
|
|
800
|
+
const written = writeCompileOutput(outputFile, result);
|
|
757
801
|
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
758
802
|
const note = changedFile ? ` — ${changedFile} changed` : "";
|
|
759
803
|
log.ok(
|
|
760
|
-
`— wrote ${
|
|
804
|
+
`— wrote ${formatWrittenDesc(written, outputFile)} ${colors.dim(`(${elapsedMs}ms${note})`)}`,
|
|
761
805
|
);
|
|
762
806
|
devServer.notify();
|
|
763
807
|
} catch (e) {
|
|
@@ -878,13 +922,13 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
878
922
|
});
|
|
879
923
|
let initialError = null;
|
|
880
924
|
|
|
925
|
+
let written = null;
|
|
881
926
|
try {
|
|
882
927
|
const result = runCompile(srcDir, isDir, {
|
|
883
928
|
outputFile,
|
|
884
929
|
sourceMap: options.sourceMap ?? true,
|
|
885
930
|
});
|
|
886
|
-
|
|
887
|
-
writeFileSync(outputFile, `${result.js}\n`);
|
|
931
|
+
written = writeCompileOutput(outputFile, result);
|
|
888
932
|
} catch (err) {
|
|
889
933
|
initialError = err;
|
|
890
934
|
devServer.notifyError(err);
|
|
@@ -914,6 +958,7 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
914
958
|
srcDir,
|
|
915
959
|
outputFile,
|
|
916
960
|
initialError,
|
|
961
|
+
written,
|
|
917
962
|
close: async () => {
|
|
918
963
|
watcherController?.close();
|
|
919
964
|
await devServer.close();
|
package/src/compiler.js
CHANGED
|
@@ -19,16 +19,23 @@
|
|
|
19
19
|
|
|
20
20
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
21
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
22
|
-
import { CodeGen } from "./
|
|
23
|
-
import {
|
|
22
|
+
import { buildSourceMap, CodeGen } from "./backend/js/index.js";
|
|
23
|
+
import { compileWasmModule } from "./backend/wasm/index.js";
|
|
24
24
|
import { log } from "./colors.js";
|
|
25
25
|
import { parseDts } from "./dts-parser.js";
|
|
26
26
|
import { Lexer } from "./lexer.js";
|
|
27
|
+
import { lower } from "./lower/index.js";
|
|
27
28
|
import { Parser } from "./parser/index.js";
|
|
28
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
isBuiltinPackage,
|
|
31
|
+
isLocalPath,
|
|
32
|
+
resolveAll,
|
|
33
|
+
resolveGwDir,
|
|
34
|
+
} from "./resolver.js";
|
|
29
35
|
import { TemplLexer } from "./templ-lexer.js";
|
|
30
36
|
import { TemplParser } from "./templ-parser.js";
|
|
31
37
|
import { TypeChecker } from "./typechecker/index.js";
|
|
38
|
+
import { WASM_SUPPORTED_STDLIB } from "./typechecker/types.js";
|
|
32
39
|
|
|
33
40
|
// ── Parse cache ──────────────────────────────────────────────
|
|
34
41
|
|
|
@@ -156,6 +163,147 @@ function attachSourceMap(merged, outputDir) {
|
|
|
156
163
|
merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
|
|
157
164
|
}
|
|
158
165
|
|
|
166
|
+
// ── WASM linking ──────────────────────────────────────────────
|
|
167
|
+
//
|
|
168
|
+
// Every `wasm`/`both` package reached during a build registers a unit in the
|
|
169
|
+
// shared `options.wasmUnits` array. The root compile links all units into a
|
|
170
|
+
// single `app.wasm` and splices the generated JS facade into the bundle where
|
|
171
|
+
// the first wasm package's JS would have been.
|
|
172
|
+
|
|
173
|
+
const WASM_MARKER_RE = /^\/\*__GOFRONT_WASM_UNIT:[^*]*\*\/$/m;
|
|
174
|
+
|
|
175
|
+
function wasmMarker(pkgName) {
|
|
176
|
+
return `/*__GOFRONT_WASM_UNIT:${pkgName}*/`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function registerWasmUnit(options, unit) {
|
|
180
|
+
const units = options.wasmUnits;
|
|
181
|
+
if (!units) return;
|
|
182
|
+
if (units.some((u) => u.key === unit.key)) return;
|
|
183
|
+
units.push(unit);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Registers a `wasm`/`both` package as a link unit and returns the target the
|
|
187
|
+
// package's own code is emitted for. `options.wasmTests` runs a `both`
|
|
188
|
+
// package's tests through the wasm backend (the package is then treated as
|
|
189
|
+
// `wasm` for linking so its test functions are exposed by the facade).
|
|
190
|
+
function registerPackageUnit(options, unit) {
|
|
191
|
+
const { pkgTarget, programs } = unit;
|
|
192
|
+
if (pkgTarget !== "wasm" && pkgTarget !== "both") return pkgTarget;
|
|
193
|
+
const emitTarget =
|
|
194
|
+
options.wasmTests && options.isTest && pkgTarget === "both"
|
|
195
|
+
? "wasm"
|
|
196
|
+
: pkgTarget;
|
|
197
|
+
registerWasmUnit(options, {
|
|
198
|
+
...unit,
|
|
199
|
+
target: emitTarget,
|
|
200
|
+
programs:
|
|
201
|
+
emitTarget === pkgTarget
|
|
202
|
+
? programs
|
|
203
|
+
: programs.map((p) => ({ ...p, target: emitTarget })),
|
|
204
|
+
});
|
|
205
|
+
return emitTarget;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function linkWasmUnits(units, { emitWat = false, rootTarget = "js" } = {}) {
|
|
209
|
+
if (!units.some((u) => u.target === "wasm")) return null;
|
|
210
|
+
checkLinkCollisions(units);
|
|
211
|
+
|
|
212
|
+
const types = new Map();
|
|
213
|
+
const bundledPackages = new Set();
|
|
214
|
+
const programs = [];
|
|
215
|
+
for (const u of units) {
|
|
216
|
+
for (const [k, v] of u.types) types.set(k, v);
|
|
217
|
+
for (const b of u.bundledPackages) bundledPackages.add(b);
|
|
218
|
+
programs.push(...u.programs);
|
|
219
|
+
}
|
|
220
|
+
const checker = { types };
|
|
221
|
+
const lowerRes = lower(programs, checker);
|
|
222
|
+
return compileWasmModule(programs, checker, lowerRes, {
|
|
223
|
+
boundary: true,
|
|
224
|
+
bundledPackages,
|
|
225
|
+
emitWat,
|
|
226
|
+
callMain: rootTarget === "wasm",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The linked module is one flat namespace (like the JS bundle), so a top-level
|
|
231
|
+
// name declared by two different packages would silently resolve to whichever
|
|
232
|
+
// was emitted last. Reject it up front.
|
|
233
|
+
function topLevelDeclNames(decl) {
|
|
234
|
+
switch (decl.kind) {
|
|
235
|
+
case "FuncDecl":
|
|
236
|
+
return [decl.name];
|
|
237
|
+
case "MethodDecl":
|
|
238
|
+
return [`${decl.recvType?.name ?? "?"}.${decl.name}`];
|
|
239
|
+
case "TypeDecl":
|
|
240
|
+
return [decl.name];
|
|
241
|
+
case "VarDecl":
|
|
242
|
+
case "ConstDecl":
|
|
243
|
+
return (decl.decls ?? []).flatMap((spec) => spec.names ?? []);
|
|
244
|
+
default:
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function checkLinkCollisions(units) {
|
|
250
|
+
const owner = new Map(); // name -> pkgName
|
|
251
|
+
const errors = [];
|
|
252
|
+
for (const u of units) {
|
|
253
|
+
for (const p of u.programs) {
|
|
254
|
+
for (const d of p.decls ?? []) {
|
|
255
|
+
for (const name of topLevelDeclNames(d)) {
|
|
256
|
+
if (name === "_") continue;
|
|
257
|
+
const prev = owner.get(name);
|
|
258
|
+
if (prev === undefined) owner.set(name, u.pkgName);
|
|
259
|
+
else if (prev !== u.pkgName)
|
|
260
|
+
errors.push(
|
|
261
|
+
`cannot link wasm packages: '${name}' is declared in both '${prev}' and '${u.pkgName}' (linked wasm packages share one namespace)`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Replaces the first wasm marker with the facade (dropping the others) and
|
|
271
|
+
// shifts source-map lines that follow it.
|
|
272
|
+
function spliceFacade(merged, facade) {
|
|
273
|
+
const lines = merged.js.split("\n");
|
|
274
|
+
const first = lines.findIndex((l) => WASM_MARKER_RE.test(l));
|
|
275
|
+
if (first < 0) {
|
|
276
|
+
merged.js = `${facade}\n${merged.js}`;
|
|
277
|
+
const shift = facade.split("\n").length;
|
|
278
|
+
for (const m of merged.mappings) m.genLine += shift;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const facadeLines = facade.split("\n");
|
|
282
|
+
const delta = facadeLines.length - 1;
|
|
283
|
+
for (const m of merged.mappings) {
|
|
284
|
+
if (m.genLine > first) m.genLine += delta;
|
|
285
|
+
}
|
|
286
|
+
const out = [...lines.slice(0, first), ...facadeLines];
|
|
287
|
+
for (let i = first + 1; i < lines.length; i++) {
|
|
288
|
+
if (!WASM_MARKER_RE.test(lines[i])) out.push(lines[i]);
|
|
289
|
+
}
|
|
290
|
+
merged.js = out.join("\n");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Root compiles only: link every registered unit into app.wasm and splice the
|
|
294
|
+
// facade into the merged bundle. Returns `{ wasm, wat }` (nulls when there
|
|
295
|
+
// is nothing to link).
|
|
296
|
+
function linkIntoBundle(options, merged, pkgTarget) {
|
|
297
|
+
if (options.isDependency) return { wasm: null, wat: null };
|
|
298
|
+
const linked = linkWasmUnits(options.wasmUnits, {
|
|
299
|
+
emitWat: options.emitWat,
|
|
300
|
+
rootTarget: pkgTarget,
|
|
301
|
+
});
|
|
302
|
+
if (!linked) return { wasm: null, wat: null };
|
|
303
|
+
spliceFacade(merged, linked.facade);
|
|
304
|
+
return { wasm: linked.wasm, wat: linked.wat ?? null };
|
|
305
|
+
}
|
|
306
|
+
|
|
159
307
|
// ── Import resolution ─────────────────────────────────────────
|
|
160
308
|
//
|
|
161
309
|
// Shared by compileFiles (multi-file) and the single-file path in index.js.
|
|
@@ -173,6 +321,55 @@ export function resolveImports(
|
|
|
173
321
|
) {
|
|
174
322
|
const fromDir = dirname(resolve(fromFile));
|
|
175
323
|
const allImports = programs.flatMap((p) => p.imports);
|
|
324
|
+
const pkgTarget = checker.target ?? "js";
|
|
325
|
+
const pkgName = checker.pkgName ?? programs[0]?.pkg?.name ?? "main";
|
|
326
|
+
|
|
327
|
+
// Validate target rules on non-local imports
|
|
328
|
+
if (pkgTarget === "wasm" || pkgTarget === "both") {
|
|
329
|
+
for (const p of programs) {
|
|
330
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
331
|
+
checker._currentFile = p._filename ?? null;
|
|
332
|
+
checker._currentSource = p._source ?? null;
|
|
333
|
+
for (const imp of p.imports) {
|
|
334
|
+
for (const { path, _line, _col, line, col } of imp.imports) {
|
|
335
|
+
const impNode = {
|
|
336
|
+
_line,
|
|
337
|
+
_col,
|
|
338
|
+
line: line ?? _line,
|
|
339
|
+
col: col ?? _col ?? 1,
|
|
340
|
+
};
|
|
341
|
+
if (path.startsWith("js:")) {
|
|
342
|
+
checker.recordBlocker("js: import", path);
|
|
343
|
+
checker.err(
|
|
344
|
+
"js: imports are not allowed in wasm packages",
|
|
345
|
+
impNode,
|
|
346
|
+
);
|
|
347
|
+
} else if (path === "gom") {
|
|
348
|
+
checker.recordBlocker("gom usage", "gom");
|
|
349
|
+
checker.err(
|
|
350
|
+
"package 'gom' is not available in wasm packages",
|
|
351
|
+
impNode,
|
|
352
|
+
);
|
|
353
|
+
} else if (!isLocalPath(path)) {
|
|
354
|
+
if (!isBuiltinPackage(path)) {
|
|
355
|
+
checker.recordBlocker("js: import", path);
|
|
356
|
+
checker.err(
|
|
357
|
+
"js: imports are not allowed in wasm packages",
|
|
358
|
+
impNode,
|
|
359
|
+
);
|
|
360
|
+
} else if (!WASM_SUPPORTED_STDLIB.has(path)) {
|
|
361
|
+
checker.recordBlocker("unsupported stdlib import", path);
|
|
362
|
+
checker.err(
|
|
363
|
+
`'${path}' is not yet available in wasm packages`,
|
|
364
|
+
impNode,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
176
373
|
|
|
177
374
|
// js: prefix — local .d.ts files
|
|
178
375
|
for (const imp of allImports) {
|
|
@@ -205,7 +402,7 @@ export function resolveImports(
|
|
|
205
402
|
const seenLocalPaths = new Set();
|
|
206
403
|
for (const p of programs) {
|
|
207
404
|
for (const imp of p.imports) {
|
|
208
|
-
for (const { path, alias, _line } of imp.imports) {
|
|
405
|
+
for (const { path, alias, _line, _col, line, col } of imp.imports) {
|
|
209
406
|
if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
|
|
210
407
|
seenLocalPaths.add(path);
|
|
211
408
|
const depDir = resolveGwDir(path, fromFile);
|
|
@@ -226,6 +423,44 @@ export function resolveImports(
|
|
|
226
423
|
isDependency: true,
|
|
227
424
|
});
|
|
228
425
|
if (!alreadyBundled) preambles.push(dep);
|
|
426
|
+
|
|
427
|
+
// Target import rules for local packages
|
|
428
|
+
const impNode = {
|
|
429
|
+
_line,
|
|
430
|
+
_col,
|
|
431
|
+
line: line ?? _line,
|
|
432
|
+
col: col ?? _col ?? 1,
|
|
433
|
+
};
|
|
434
|
+
if (pkgTarget === "wasm" && dep.target === "js") {
|
|
435
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
436
|
+
checker._currentFile = p._filename ?? null;
|
|
437
|
+
checker._currentSource = p._source ?? null;
|
|
438
|
+
checker.recordBlocker("js package import", dep.pkgName);
|
|
439
|
+
checker.err(
|
|
440
|
+
`package '${pkgName}' (wasm) cannot import '${dep.pkgName}' (js)`,
|
|
441
|
+
impNode,
|
|
442
|
+
);
|
|
443
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
444
|
+
} else if (pkgTarget === "both") {
|
|
445
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
446
|
+
checker._currentFile = p._filename ?? null;
|
|
447
|
+
checker._currentSource = p._source ?? null;
|
|
448
|
+
if (dep.target === "js") {
|
|
449
|
+
checker.recordBlocker("js package import", dep.pkgName);
|
|
450
|
+
checker.err(
|
|
451
|
+
`package '${pkgName}' (both) cannot import '${dep.pkgName}' (js)`,
|
|
452
|
+
impNode,
|
|
453
|
+
);
|
|
454
|
+
} else if (dep.target === "wasm") {
|
|
455
|
+
checker.recordBlocker("wasm package import", dep.pkgName);
|
|
456
|
+
checker.err(
|
|
457
|
+
`package '${pkgName}' (both) can only import 'both' packages; '${dep.pkgName}' is wasm`,
|
|
458
|
+
impNode,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
462
|
+
}
|
|
463
|
+
|
|
229
464
|
if (alias === "_") continue;
|
|
230
465
|
if (alias === ".") {
|
|
231
466
|
checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
|
|
@@ -253,6 +488,7 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
253
488
|
dumpTokens = false,
|
|
254
489
|
dumpAst = false,
|
|
255
490
|
} = options;
|
|
491
|
+
options = { ...options, wasmUnits: options.wasmUnits ?? [] };
|
|
256
492
|
|
|
257
493
|
let source;
|
|
258
494
|
try {
|
|
@@ -277,6 +513,11 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
277
513
|
const bundledPackages = new Set();
|
|
278
514
|
const preambles = [];
|
|
279
515
|
|
|
516
|
+
const pkgTarget = ast.target ?? options.target ?? "js";
|
|
517
|
+
ast.target = pkgTarget;
|
|
518
|
+
checker.target = pkgTarget;
|
|
519
|
+
checker.pkgName = ast.pkg?.name ?? "main";
|
|
520
|
+
|
|
280
521
|
resolveImports(
|
|
281
522
|
[ast],
|
|
282
523
|
inputPath,
|
|
@@ -294,23 +535,40 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
294
535
|
throw new Error(errors.map((e) => e.message).join("\n"));
|
|
295
536
|
}
|
|
296
537
|
|
|
297
|
-
|
|
298
|
-
|
|
538
|
+
if (pkgTarget === "wasm" || pkgTarget === "both") {
|
|
539
|
+
registerWasmUnit(options, {
|
|
540
|
+
key: resolve(inputPath),
|
|
541
|
+
pkgName: checker.pkgName,
|
|
542
|
+
target: pkgTarget,
|
|
543
|
+
programs: [ast],
|
|
544
|
+
types: checker.types,
|
|
545
|
+
bundledPackages,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const isStrict = Boolean(options.strict || pkgTarget === "both");
|
|
550
|
+
const cg = new CodeGen(checker, jsImports, bundledPackages, {
|
|
551
|
+
target: pkgTarget,
|
|
552
|
+
strict: isStrict,
|
|
553
|
+
});
|
|
554
|
+
const mainJs =
|
|
555
|
+
pkgTarget === "wasm" ? wasmMarker(checker.pkgName) : cg.generate(ast);
|
|
299
556
|
|
|
300
557
|
const mainChunk = {
|
|
301
558
|
js: mainJs,
|
|
302
559
|
sourceFiles: [resolve(inputPath)],
|
|
303
560
|
sourcesContent: [source],
|
|
304
|
-
mappings: cg.getMappings(),
|
|
561
|
+
mappings: pkgTarget === "wasm" ? [] : cg.getMappings(),
|
|
305
562
|
};
|
|
306
563
|
|
|
307
564
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
565
|
+
const { wasm, wat } = linkIntoBundle(options, merged, pkgTarget);
|
|
308
566
|
|
|
309
567
|
if (sourceMap && !options.isDependency) {
|
|
310
568
|
attachSourceMap(merged, outputDir);
|
|
311
569
|
}
|
|
312
570
|
|
|
313
|
-
return { js: merged.js, css: cg.getCss() };
|
|
571
|
+
return { js: merged.js, css: cg.getCss(), target: pkgTarget, wasm, wat };
|
|
314
572
|
}
|
|
315
573
|
|
|
316
574
|
export function compileDir(dir, options = {}) {
|
|
@@ -325,7 +583,15 @@ export function compilePackageTests(dir, options = {}) {
|
|
|
325
583
|
return compileFiles(files, { ...options, fromDir: dir, isTest: true });
|
|
326
584
|
}
|
|
327
585
|
|
|
586
|
+
// Compiles a `both` package's tests through the wasm backend (the JS run uses
|
|
587
|
+
// compilePackageTests). Returns null for packages that are not `both`.
|
|
588
|
+
export function compilePackageTestsWasm(dir, options = {}) {
|
|
589
|
+
const res = compilePackageTests(dir, { ...options, wasmTests: true });
|
|
590
|
+
return res.target === "both" ? res : null;
|
|
591
|
+
}
|
|
592
|
+
|
|
328
593
|
export function compileFiles(files, options = {}) {
|
|
594
|
+
options = { ...options, wasmUnits: options.wasmUnits ?? [] };
|
|
329
595
|
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
330
596
|
const outputDir =
|
|
331
597
|
options.outputDir ??
|
|
@@ -353,8 +619,32 @@ export function compileFiles(files, options = {}) {
|
|
|
353
619
|
);
|
|
354
620
|
const pkgName = pkgNames[0];
|
|
355
621
|
|
|
622
|
+
// Validate target directives across files in this package
|
|
623
|
+
const declaredTargets = new Map();
|
|
624
|
+
for (const p of programs) {
|
|
625
|
+
if (p.target) {
|
|
626
|
+
if (!declaredTargets.has(p.target)) declaredTargets.set(p.target, []);
|
|
627
|
+
declaredTargets.get(p.target).push(p._filename);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (declaredTargets.size > 1) {
|
|
631
|
+
const list = [...declaredTargets.entries()]
|
|
632
|
+
.map(([t, fList]) => `${t} (${fList.join(", ")})`)
|
|
633
|
+
.join(" vs ");
|
|
634
|
+
throw new Error(
|
|
635
|
+
`Conflicting //gofront:target directives in package '${pkgName}': ${list}`,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const pkgTarget =
|
|
639
|
+
declaredTargets.size === 1
|
|
640
|
+
? declaredTargets.keys().next().value
|
|
641
|
+
: (options.target ?? "js");
|
|
642
|
+
for (const p of programs) p.target = pkgTarget;
|
|
643
|
+
|
|
356
644
|
// ── 2. Resolve imports ────────────────────────────────────────
|
|
357
645
|
const checker = new TypeChecker();
|
|
646
|
+
checker.target = pkgTarget;
|
|
647
|
+
checker.pkgName = pkgName;
|
|
358
648
|
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
359
649
|
const bundledPackages = new Set(); // package names whose code is inlined
|
|
360
650
|
const preambles = []; // JS code / chunks from compiled sub-packages
|
|
@@ -380,19 +670,36 @@ export function compileFiles(files, options = {}) {
|
|
|
380
670
|
}
|
|
381
671
|
|
|
382
672
|
// ── 4. Code generation ────────────────────────────────────────
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
673
|
+
const emitTarget = registerPackageUnit(options, {
|
|
674
|
+
key: resolve(fromDir),
|
|
675
|
+
pkgName,
|
|
676
|
+
pkgTarget,
|
|
677
|
+
programs,
|
|
678
|
+
types: checker.types,
|
|
679
|
+
bundledPackages,
|
|
386
680
|
});
|
|
387
681
|
|
|
682
|
+
const isStrict = Boolean(options.strict || pkgTarget === "both");
|
|
683
|
+
const codegen = new CodeGen(checker, jsImports, bundledPackages, {
|
|
684
|
+
target: pkgTarget,
|
|
685
|
+
strict: isStrict,
|
|
686
|
+
});
|
|
687
|
+
const mainJs =
|
|
688
|
+
emitTarget === "wasm"
|
|
689
|
+
? wasmMarker(pkgName)
|
|
690
|
+
: codegen.generateAll(programs, {
|
|
691
|
+
isTest: options.isTest ?? false,
|
|
692
|
+
});
|
|
693
|
+
|
|
388
694
|
const mainChunk = {
|
|
389
695
|
js: mainJs,
|
|
390
696
|
sourceFiles: files.map((f) => resolve(f)),
|
|
391
697
|
sourcesContent: files.map((f) => readFileSync(f, "utf8")),
|
|
392
|
-
mappings: codegen.getMappings(),
|
|
698
|
+
mappings: emitTarget === "wasm" ? [] : codegen.getMappings(),
|
|
393
699
|
};
|
|
394
700
|
|
|
395
701
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
702
|
+
const { wasm, wat } = linkIntoBundle(options, merged, pkgTarget);
|
|
396
703
|
|
|
397
704
|
if (options.sourceMap && !options.isDependency) {
|
|
398
705
|
attachSourceMap(merged, outputDir);
|
|
@@ -405,8 +712,11 @@ export function compileFiles(files, options = {}) {
|
|
|
405
712
|
|
|
406
713
|
return {
|
|
407
714
|
pkgName,
|
|
715
|
+
target: pkgTarget,
|
|
408
716
|
js: merged.js,
|
|
409
717
|
css: allCss,
|
|
718
|
+
wasm,
|
|
719
|
+
wat,
|
|
410
720
|
programs,
|
|
411
721
|
exportedSymbols: checker.getExportedSymbols(),
|
|
412
722
|
exportedTypes: checker.getExportedTypes(),
|