gofront 1.4.0 → 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 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- 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 +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -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 +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /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/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/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.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/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/src/compiler.js
CHANGED
|
@@ -18,17 +18,28 @@
|
|
|
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";
|
|
31
|
+
import { lower } from "./lower/index.js";
|
|
27
32
|
import { Parser } from "./parser/index.js";
|
|
28
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
isBuiltinPackage,
|
|
35
|
+
isLocalPath,
|
|
36
|
+
resolveAll,
|
|
37
|
+
resolveGwDir,
|
|
38
|
+
} from "./resolver.js";
|
|
29
39
|
import { TemplLexer } from "./templ-lexer.js";
|
|
30
40
|
import { TemplParser } from "./templ-parser.js";
|
|
31
41
|
import { TypeChecker } from "./typechecker/index.js";
|
|
42
|
+
import { WASM_SUPPORTED_STDLIB } from "./typechecker/types.js";
|
|
32
43
|
|
|
33
44
|
// ── Parse cache ──────────────────────────────────────────────
|
|
34
45
|
|
|
@@ -60,6 +71,9 @@ function parseGoFrontFile(filePath) {
|
|
|
60
71
|
? new TemplParser(tokens, filename, source).parse()
|
|
61
72
|
: new Parser(tokens, filename, source).parse();
|
|
62
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;
|
|
63
77
|
_parseCache.set(filePath, { mtime, ast });
|
|
64
78
|
return ast;
|
|
65
79
|
}
|
|
@@ -156,12 +170,205 @@ function attachSourceMap(merged, outputDir) {
|
|
|
156
170
|
merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
|
|
157
171
|
}
|
|
158
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
|
+
|
|
215
|
+
// ── WASM linking ──────────────────────────────────────────────
|
|
216
|
+
//
|
|
217
|
+
// Every `wasm`/`both` package reached during a build registers a unit in the
|
|
218
|
+
// shared `options.wasmUnits` array. The root compile links all units into a
|
|
219
|
+
// single `app.wasm` and splices the generated JS facade into the bundle where
|
|
220
|
+
// the first wasm package's JS would have been.
|
|
221
|
+
|
|
222
|
+
const WASM_MARKER_RE = /^\/\*__GOFRONT_WASM_UNIT:[^*]*\*\/$/m;
|
|
223
|
+
|
|
224
|
+
function wasmMarker(pkgName) {
|
|
225
|
+
return `/*__GOFRONT_WASM_UNIT:${pkgName}*/`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function registerWasmUnit(options, unit) {
|
|
229
|
+
const units = options.wasmUnits;
|
|
230
|
+
if (!units) return;
|
|
231
|
+
if (units.some((u) => u.key === unit.key)) return;
|
|
232
|
+
units.push(unit);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Registers a `wasm`/`both` package as a link unit and returns the target the
|
|
236
|
+
// package's own code is emitted for. `options.wasmTests` runs a `both`
|
|
237
|
+
// package's tests through the wasm backend (the package is then treated as
|
|
238
|
+
// `wasm` for linking so its test functions are exposed by the facade).
|
|
239
|
+
function registerPackageUnit(options, unit) {
|
|
240
|
+
const { pkgTarget, programs } = unit;
|
|
241
|
+
if (pkgTarget !== "wasm" && pkgTarget !== "both") return pkgTarget;
|
|
242
|
+
const emitTarget =
|
|
243
|
+
options.wasmTests && options.isTest && pkgTarget === "both"
|
|
244
|
+
? "wasm"
|
|
245
|
+
: pkgTarget;
|
|
246
|
+
registerWasmUnit(options, {
|
|
247
|
+
...unit,
|
|
248
|
+
target: emitTarget,
|
|
249
|
+
programs:
|
|
250
|
+
emitTarget === pkgTarget
|
|
251
|
+
? programs
|
|
252
|
+
: programs.map((p) => ({ ...p, target: emitTarget })),
|
|
253
|
+
});
|
|
254
|
+
return emitTarget;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function linkWasmUnits(
|
|
258
|
+
units,
|
|
259
|
+
{ emitWat = false, rootTarget = "js", sourceMappingURL = null } = {},
|
|
260
|
+
) {
|
|
261
|
+
if (!units.some((u) => u.target === "wasm")) return null;
|
|
262
|
+
checkLinkCollisions(units);
|
|
263
|
+
|
|
264
|
+
const types = new Map();
|
|
265
|
+
const bundledPackages = new Set();
|
|
266
|
+
const programs = [];
|
|
267
|
+
for (const u of units) {
|
|
268
|
+
for (const [k, v] of u.types) types.set(k, v);
|
|
269
|
+
for (const b of u.bundledPackages) bundledPackages.add(b);
|
|
270
|
+
programs.push(...u.programs);
|
|
271
|
+
}
|
|
272
|
+
const checker = { types };
|
|
273
|
+
const lowerRes = lower(programs, checker);
|
|
274
|
+
return compileWasmModule(programs, checker, lowerRes, {
|
|
275
|
+
boundary: true,
|
|
276
|
+
bundledPackages,
|
|
277
|
+
emitWat,
|
|
278
|
+
callMain: rootTarget === "wasm",
|
|
279
|
+
sourceMappingURL,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The linked module is one flat namespace (like the JS bundle), so a top-level
|
|
284
|
+
// name declared by two different packages would silently resolve to whichever
|
|
285
|
+
// was emitted last. Reject it up front.
|
|
286
|
+
function topLevelDeclNames(decl) {
|
|
287
|
+
switch (decl.kind) {
|
|
288
|
+
case "FuncDecl":
|
|
289
|
+
return [decl.name];
|
|
290
|
+
case "MethodDecl":
|
|
291
|
+
return [`${decl.recvType?.name ?? "?"}.${decl.name}`];
|
|
292
|
+
case "TypeDecl":
|
|
293
|
+
return [decl.name];
|
|
294
|
+
case "VarDecl":
|
|
295
|
+
case "ConstDecl":
|
|
296
|
+
return (decl.decls ?? []).flatMap((spec) => spec.names ?? []);
|
|
297
|
+
default:
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function checkLinkCollisions(units) {
|
|
303
|
+
const owner = new Map(); // name -> pkgName
|
|
304
|
+
const errors = [];
|
|
305
|
+
for (const u of units) {
|
|
306
|
+
for (const p of u.programs) {
|
|
307
|
+
for (const d of p.decls ?? []) {
|
|
308
|
+
for (const name of topLevelDeclNames(d)) {
|
|
309
|
+
if (name === "_") continue;
|
|
310
|
+
const prev = owner.get(name);
|
|
311
|
+
if (prev === undefined) owner.set(name, u.pkgName);
|
|
312
|
+
else if (prev !== u.pkgName)
|
|
313
|
+
errors.push(
|
|
314
|
+
`cannot link wasm packages: '${name}' is declared in both '${prev}' and '${u.pkgName}' (linked wasm packages share one namespace)`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Replaces the first wasm marker with the facade (dropping the others) and
|
|
324
|
+
// shifts source-map lines that follow it.
|
|
325
|
+
function spliceFacade(merged, facade) {
|
|
326
|
+
const lines = merged.js.split("\n");
|
|
327
|
+
const first = lines.findIndex((l) => WASM_MARKER_RE.test(l));
|
|
328
|
+
if (first < 0) {
|
|
329
|
+
merged.js = `${facade}\n${merged.js}`;
|
|
330
|
+
const shift = facade.split("\n").length;
|
|
331
|
+
for (const m of merged.mappings) m.genLine += shift;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const facadeLines = facade.split("\n");
|
|
335
|
+
const delta = facadeLines.length - 1;
|
|
336
|
+
for (const m of merged.mappings) {
|
|
337
|
+
if (m.genLine > first) m.genLine += delta;
|
|
338
|
+
}
|
|
339
|
+
const out = [...lines.slice(0, first), ...facadeLines];
|
|
340
|
+
for (let i = first + 1; i < lines.length; i++) {
|
|
341
|
+
if (!WASM_MARKER_RE.test(lines[i])) out.push(lines[i]);
|
|
342
|
+
}
|
|
343
|
+
merged.js = out.join("\n");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Root compiles only: link every registered unit into app.wasm and splice the
|
|
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;
|
|
352
|
+
const linked = linkWasmUnits(options.wasmUnits, {
|
|
353
|
+
emitWat: options.emitWat,
|
|
354
|
+
rootTarget: pkgTarget,
|
|
355
|
+
sourceMappingURL,
|
|
356
|
+
});
|
|
357
|
+
if (!linked) return none;
|
|
358
|
+
spliceFacade(merged, linked.facade);
|
|
359
|
+
return {
|
|
360
|
+
wasm: linked.wasm,
|
|
361
|
+
wat: linked.wat ?? null,
|
|
362
|
+
sourceMapInfo: linked.sourceMapInfo ?? null,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
159
366
|
// ── Import resolution ─────────────────────────────────────────
|
|
160
367
|
//
|
|
161
368
|
// Shared by compileFiles (multi-file) and the single-file path in index.js.
|
|
162
369
|
// Mutates checker, jsImports, bundledPackages, and preambles in place.
|
|
163
370
|
|
|
164
|
-
|
|
371
|
+
function resolveImports(
|
|
165
372
|
programs,
|
|
166
373
|
fromFile,
|
|
167
374
|
checker,
|
|
@@ -173,6 +380,60 @@ export function resolveImports(
|
|
|
173
380
|
) {
|
|
174
381
|
const fromDir = dirname(resolve(fromFile));
|
|
175
382
|
const allImports = programs.flatMap((p) => p.imports);
|
|
383
|
+
const pkgTarget = checker.target ?? "js";
|
|
384
|
+
const pkgName = checker.pkgName ?? programs[0]?.pkg?.name ?? "main";
|
|
385
|
+
|
|
386
|
+
// Validate target rules on non-local imports
|
|
387
|
+
if (pkgTarget === "wasm" || pkgTarget === "both") {
|
|
388
|
+
for (const p of programs) {
|
|
389
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
390
|
+
checker._currentFile = p._filename ?? null;
|
|
391
|
+
checker._currentSource = p._source ?? null;
|
|
392
|
+
for (const imp of p.imports) {
|
|
393
|
+
for (const { path, _line, _col, line, col } of imp.imports) {
|
|
394
|
+
const impNode = {
|
|
395
|
+
_line,
|
|
396
|
+
_col,
|
|
397
|
+
line: line ?? _line,
|
|
398
|
+
col: col ?? _col ?? 1,
|
|
399
|
+
};
|
|
400
|
+
if (path.startsWith("js:")) {
|
|
401
|
+
checker.recordBlocker("js: import", path);
|
|
402
|
+
checker.err(
|
|
403
|
+
"js: imports are not allowed in wasm packages",
|
|
404
|
+
impNode,
|
|
405
|
+
);
|
|
406
|
+
} else if (path === "gom") {
|
|
407
|
+
checker.recordBlocker("gom usage", "gom");
|
|
408
|
+
checker.err(
|
|
409
|
+
"package 'gom' is not available in wasm packages",
|
|
410
|
+
impNode,
|
|
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
|
+
);
|
|
417
|
+
} else if (!isLocalPath(path)) {
|
|
418
|
+
if (!isBuiltinPackage(path)) {
|
|
419
|
+
checker.recordBlocker("js: import", path);
|
|
420
|
+
checker.err(
|
|
421
|
+
"js: imports are not allowed in wasm packages",
|
|
422
|
+
impNode,
|
|
423
|
+
);
|
|
424
|
+
} else if (!WASM_SUPPORTED_STDLIB.has(path)) {
|
|
425
|
+
checker.recordBlocker("unsupported stdlib import", path);
|
|
426
|
+
checker.err(
|
|
427
|
+
`'${path}' is not yet available in wasm packages`,
|
|
428
|
+
impNode,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
176
437
|
|
|
177
438
|
// js: prefix — local .d.ts files
|
|
178
439
|
for (const imp of allImports) {
|
|
@@ -205,7 +466,7 @@ export function resolveImports(
|
|
|
205
466
|
const seenLocalPaths = new Set();
|
|
206
467
|
for (const p of programs) {
|
|
207
468
|
for (const imp of p.imports) {
|
|
208
|
-
for (const { path, alias, _line } of imp.imports) {
|
|
469
|
+
for (const { path, alias, _line, _col, line, col } of imp.imports) {
|
|
209
470
|
if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
|
|
210
471
|
seenLocalPaths.add(path);
|
|
211
472
|
const depDir = resolveGwDir(path, fromFile);
|
|
@@ -226,6 +487,44 @@ export function resolveImports(
|
|
|
226
487
|
isDependency: true,
|
|
227
488
|
});
|
|
228
489
|
if (!alreadyBundled) preambles.push(dep);
|
|
490
|
+
|
|
491
|
+
// Target import rules for local packages
|
|
492
|
+
const impNode = {
|
|
493
|
+
_line,
|
|
494
|
+
_col,
|
|
495
|
+
line: line ?? _line,
|
|
496
|
+
col: col ?? _col ?? 1,
|
|
497
|
+
};
|
|
498
|
+
if (pkgTarget === "wasm" && dep.target === "js") {
|
|
499
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
500
|
+
checker._currentFile = p._filename ?? null;
|
|
501
|
+
checker._currentSource = p._source ?? null;
|
|
502
|
+
checker.recordBlocker("js package import", dep.pkgName);
|
|
503
|
+
checker.err(
|
|
504
|
+
`package '${pkgName}' (wasm) cannot import '${dep.pkgName}' (js)`,
|
|
505
|
+
impNode,
|
|
506
|
+
);
|
|
507
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
508
|
+
} else if (pkgTarget === "both") {
|
|
509
|
+
const saved = [checker._currentFile, checker._currentSource];
|
|
510
|
+
checker._currentFile = p._filename ?? null;
|
|
511
|
+
checker._currentSource = p._source ?? null;
|
|
512
|
+
if (dep.target === "js") {
|
|
513
|
+
checker.recordBlocker("js package import", dep.pkgName);
|
|
514
|
+
checker.err(
|
|
515
|
+
`package '${pkgName}' (both) cannot import '${dep.pkgName}' (js)`,
|
|
516
|
+
impNode,
|
|
517
|
+
);
|
|
518
|
+
} else if (dep.target === "wasm") {
|
|
519
|
+
checker.recordBlocker("wasm package import", dep.pkgName);
|
|
520
|
+
checker.err(
|
|
521
|
+
`package '${pkgName}' (both) can only import 'both' packages; '${dep.pkgName}' is wasm`,
|
|
522
|
+
impNode,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
[checker._currentFile, checker._currentSource] = saved;
|
|
526
|
+
}
|
|
527
|
+
|
|
229
528
|
if (alias === "_") continue;
|
|
230
529
|
if (alias === ".") {
|
|
231
530
|
checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
|
|
@@ -237,6 +536,7 @@ export function resolveImports(
|
|
|
237
536
|
nameUsed,
|
|
238
537
|
dep.exportedSymbols,
|
|
239
538
|
dep.exportedTypes,
|
|
539
|
+
dep.target,
|
|
240
540
|
);
|
|
241
541
|
checker.trackImport(nameUsed, { _line }, p._filename, p._source);
|
|
242
542
|
}
|
|
@@ -246,6 +546,29 @@ export function resolveImports(
|
|
|
246
546
|
|
|
247
547
|
// ── Main entry points ─────────────────────────────────────────
|
|
248
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
|
+
|
|
249
572
|
export function compileSingleFile(inputPath, options = {}) {
|
|
250
573
|
const {
|
|
251
574
|
sourceMap = false,
|
|
@@ -253,6 +576,7 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
253
576
|
dumpTokens = false,
|
|
254
577
|
dumpAst = false,
|
|
255
578
|
} = options;
|
|
579
|
+
options = { ...options, wasmUnits: options.wasmUnits ?? [] };
|
|
256
580
|
|
|
257
581
|
let source;
|
|
258
582
|
try {
|
|
@@ -266,6 +590,7 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
266
590
|
|
|
267
591
|
const ast = new Parser(tokens, basename(inputPath), source).parse();
|
|
268
592
|
ast._source = source;
|
|
593
|
+
ast._path = resolve(inputPath);
|
|
269
594
|
if (dumpAst) return { ast };
|
|
270
595
|
|
|
271
596
|
const outputDir =
|
|
@@ -277,6 +602,15 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
277
602
|
const bundledPackages = new Set();
|
|
278
603
|
const preambles = [];
|
|
279
604
|
|
|
605
|
+
const pkgTarget = resolvePackageTarget(
|
|
606
|
+
ast.target ?? options.target ?? "js",
|
|
607
|
+
dirname(resolve(inputPath)),
|
|
608
|
+
options,
|
|
609
|
+
);
|
|
610
|
+
ast.target = pkgTarget;
|
|
611
|
+
checker.target = pkgTarget;
|
|
612
|
+
checker.pkgName = ast.pkg?.name ?? "main";
|
|
613
|
+
|
|
280
614
|
resolveImports(
|
|
281
615
|
[ast],
|
|
282
616
|
inputPath,
|
|
@@ -293,24 +627,55 @@ export function compileSingleFile(inputPath, options = {}) {
|
|
|
293
627
|
if (errors.length > 0) {
|
|
294
628
|
throw new Error(errors.map((e) => e.message).join("\n"));
|
|
295
629
|
}
|
|
630
|
+
for (const w of checker.warnings) log.warn(w);
|
|
631
|
+
|
|
632
|
+
if (pkgTarget === "wasm" || pkgTarget === "both") {
|
|
633
|
+
registerWasmUnit(options, {
|
|
634
|
+
key: resolve(inputPath),
|
|
635
|
+
pkgName: checker.pkgName,
|
|
636
|
+
target: pkgTarget,
|
|
637
|
+
programs: [ast],
|
|
638
|
+
types: checker.types,
|
|
639
|
+
bundledPackages,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
296
642
|
|
|
297
|
-
const
|
|
298
|
-
const
|
|
643
|
+
const isStrict = Boolean(options.strict || pkgTarget === "both");
|
|
644
|
+
const cg = new CodeGen(checker, jsImports, bundledPackages, {
|
|
645
|
+
target: pkgTarget,
|
|
646
|
+
strict: isStrict,
|
|
647
|
+
});
|
|
648
|
+
const mainJs =
|
|
649
|
+
pkgTarget === "wasm" ? wasmMarker(checker.pkgName) : cg.generate(ast);
|
|
299
650
|
|
|
300
651
|
const mainChunk = {
|
|
301
652
|
js: mainJs,
|
|
302
653
|
sourceFiles: [resolve(inputPath)],
|
|
303
654
|
sourcesContent: [source],
|
|
304
|
-
mappings: cg.getMappings(),
|
|
655
|
+
mappings: pkgTarget === "wasm" ? [] : cg.getMappings(),
|
|
305
656
|
};
|
|
306
657
|
|
|
307
658
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
659
|
+
const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
|
|
660
|
+
{ ...options, sourceMap },
|
|
661
|
+
merged,
|
|
662
|
+
pkgTarget,
|
|
663
|
+
outputDir,
|
|
664
|
+
);
|
|
308
665
|
|
|
309
666
|
if (sourceMap && !options.isDependency) {
|
|
310
667
|
attachSourceMap(merged, outputDir);
|
|
311
668
|
}
|
|
312
669
|
|
|
313
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
js: merged.js,
|
|
672
|
+
css: cg.getCss(),
|
|
673
|
+
target: pkgTarget,
|
|
674
|
+
wasm,
|
|
675
|
+
wat,
|
|
676
|
+
wasmOptInfo,
|
|
677
|
+
wasmSourceMap,
|
|
678
|
+
};
|
|
314
679
|
}
|
|
315
680
|
|
|
316
681
|
export function compileDir(dir, options = {}) {
|
|
@@ -325,8 +690,20 @@ export function compilePackageTests(dir, options = {}) {
|
|
|
325
690
|
return compileFiles(files, { ...options, fromDir: dir, isTest: true });
|
|
326
691
|
}
|
|
327
692
|
|
|
693
|
+
// Compiles a `both` package's tests through the wasm backend (the JS run uses
|
|
694
|
+
// compilePackageTests). Returns null for packages that are not `both`.
|
|
695
|
+
export function compilePackageTestsWasm(dir, options = {}) {
|
|
696
|
+
const res = compilePackageTests(dir, { ...options, wasmTests: true });
|
|
697
|
+
return res.target === "both" ? res : null;
|
|
698
|
+
}
|
|
699
|
+
|
|
328
700
|
export function compileFiles(files, options = {}) {
|
|
329
701
|
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
702
|
+
options = {
|
|
703
|
+
...options,
|
|
704
|
+
wasmUnits: options.wasmUnits ?? [],
|
|
705
|
+
rootDir: options.rootDir ?? fromDir,
|
|
706
|
+
};
|
|
330
707
|
const outputDir =
|
|
331
708
|
options.outputDir ??
|
|
332
709
|
(options.outputFile ? dirname(resolve(options.outputFile)) : fromDir);
|
|
@@ -353,8 +730,36 @@ export function compileFiles(files, options = {}) {
|
|
|
353
730
|
);
|
|
354
731
|
const pkgName = pkgNames[0];
|
|
355
732
|
|
|
733
|
+
// Validate target directives across files in this package
|
|
734
|
+
const declaredTargets = new Map();
|
|
735
|
+
for (const p of programs) {
|
|
736
|
+
const declared = p._declaredTarget;
|
|
737
|
+
if (declared) {
|
|
738
|
+
if (!declaredTargets.has(declared)) declaredTargets.set(declared, []);
|
|
739
|
+
declaredTargets.get(declared).push(p._filename);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (declaredTargets.size > 1) {
|
|
743
|
+
const list = [...declaredTargets.entries()]
|
|
744
|
+
.map(([t, fList]) => `${t} (${fList.join(", ")})`)
|
|
745
|
+
.join(" vs ");
|
|
746
|
+
throw new Error(
|
|
747
|
+
`Conflicting //gofront:target directives in package '${pkgName}': ${list}`,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
const pkgTarget = resolvePackageTarget(
|
|
751
|
+
declaredTargets.size === 1
|
|
752
|
+
? declaredTargets.keys().next().value
|
|
753
|
+
: (options.target ?? "js"),
|
|
754
|
+
fromDir,
|
|
755
|
+
options,
|
|
756
|
+
);
|
|
757
|
+
for (const p of programs) p.target = pkgTarget;
|
|
758
|
+
|
|
356
759
|
// ── 2. Resolve imports ────────────────────────────────────────
|
|
357
760
|
const checker = new TypeChecker();
|
|
761
|
+
checker.target = pkgTarget;
|
|
762
|
+
checker.pkgName = pkgName;
|
|
358
763
|
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
359
764
|
const bundledPackages = new Set(); // package names whose code is inlined
|
|
360
765
|
const preambles = []; // JS code / chunks from compiled sub-packages
|
|
@@ -378,21 +783,44 @@ export function compileFiles(files, options = {}) {
|
|
|
378
783
|
const msgs = errors.map((e) => e.message).join("\n");
|
|
379
784
|
throw new Error(msgs);
|
|
380
785
|
}
|
|
786
|
+
for (const w of checker.warnings) log.warn(w);
|
|
381
787
|
|
|
382
788
|
// ── 4. Code generation ────────────────────────────────────────
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
789
|
+
const emitTarget = registerPackageUnit(options, {
|
|
790
|
+
key: resolve(fromDir),
|
|
791
|
+
pkgName,
|
|
792
|
+
pkgTarget,
|
|
793
|
+
programs,
|
|
794
|
+
types: checker.types,
|
|
795
|
+
bundledPackages,
|
|
386
796
|
});
|
|
387
797
|
|
|
798
|
+
const isStrict = Boolean(options.strict || pkgTarget === "both");
|
|
799
|
+
const codegen = new CodeGen(checker, jsImports, bundledPackages, {
|
|
800
|
+
target: pkgTarget,
|
|
801
|
+
strict: isStrict,
|
|
802
|
+
});
|
|
803
|
+
const mainJs =
|
|
804
|
+
emitTarget === "wasm"
|
|
805
|
+
? wasmMarker(pkgName)
|
|
806
|
+
: codegen.generateAll(programs, {
|
|
807
|
+
isTest: options.isTest ?? false,
|
|
808
|
+
});
|
|
809
|
+
|
|
388
810
|
const mainChunk = {
|
|
389
811
|
js: mainJs,
|
|
390
812
|
sourceFiles: files.map((f) => resolve(f)),
|
|
391
813
|
sourcesContent: files.map((f) => readFileSync(f, "utf8")),
|
|
392
|
-
mappings: codegen.getMappings(),
|
|
814
|
+
mappings: emitTarget === "wasm" ? [] : codegen.getMappings(),
|
|
393
815
|
};
|
|
394
816
|
|
|
395
817
|
const merged = mergeCompilationChunks([...preambles, mainChunk]);
|
|
818
|
+
const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
|
|
819
|
+
options,
|
|
820
|
+
merged,
|
|
821
|
+
pkgTarget,
|
|
822
|
+
outputDir,
|
|
823
|
+
);
|
|
396
824
|
|
|
397
825
|
if (options.sourceMap && !options.isDependency) {
|
|
398
826
|
attachSourceMap(merged, outputDir);
|
|
@@ -405,8 +833,13 @@ export function compileFiles(files, options = {}) {
|
|
|
405
833
|
|
|
406
834
|
return {
|
|
407
835
|
pkgName,
|
|
836
|
+
target: pkgTarget,
|
|
408
837
|
js: merged.js,
|
|
409
838
|
css: allCss,
|
|
839
|
+
wasm,
|
|
840
|
+
wat,
|
|
841
|
+
wasmOptInfo,
|
|
842
|
+
wasmSourceMap,
|
|
410
843
|
programs,
|
|
411
844
|
exportedSymbols: checker.getExportedSymbols(),
|
|
412
845
|
exportedTypes: checker.getExportedTypes(),
|