gofront 1.3.5 → 1.3.7
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 +7 -6
- package/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +216 -25
- package/src/codegen/runtime.js +22 -1
- package/src/codegen/statements.js +170 -45
- package/src/codegen/stdlib/builder.js +7 -2
- package/src/codegen/stdlib/fmt.js +2 -2
- package/src/codegen/stdlib/io.js +1 -1
- package/src/dev-server.js +22 -10
- package/src/index.js +8 -2
- package/src/parser/declarations.js +2 -1
- package/src/pwa.js +3 -1
- package/src/typechecker/assignability.js +50 -4
- package/src/typechecker/expressions.js +13 -1
- package/src/typechecker/index.js +50 -3
- package/src/typechecker/resolve.js +2 -0
- package/src/typechecker/statements.js +10 -1
- package/src/typechecker/stdlib/web.js +54 -26
- package/src/typechecker/types.js +3 -2
- package/src/vendor.js +12 -7
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ identifier, literal, `)`, `]`, `}`, or certain keywords (`return`, `break`, `con
|
|
|
79
79
|
`fallthrough`). This is why Go doesn't need explicit semicolons — and neither does
|
|
80
80
|
GoFront.
|
|
81
81
|
|
|
82
|
-
### 2. Parser (`src/parser
|
|
82
|
+
### 2. Parser (`src/parser/`)
|
|
83
83
|
|
|
84
84
|
A hand-written recursive-descent parser. No parser generators, no grammar files — just
|
|
85
85
|
straightforward top-down parsing. Produces an AST where every node is a plain JS object
|
|
@@ -88,7 +88,7 @@ with a `kind` field (`"FuncDecl"`, `"IfStmt"`, `"BinaryExpr"`, etc.).
|
|
|
88
88
|
Operator precedence is handled via a Pratt-style expression parser with numeric
|
|
89
89
|
precedence levels.
|
|
90
90
|
|
|
91
|
-
### 3. Type Checker (`src/typechecker
|
|
91
|
+
### 3. Type Checker (`src/typechecker/`)
|
|
92
92
|
|
|
93
93
|
Three-pass type checker operating on the AST:
|
|
94
94
|
|
|
@@ -103,7 +103,7 @@ Types are plain JS objects (`{ kind: "basic", name: "int" }`, `{ kind: "slice",
|
|
|
103
103
|
elem: ... }`, etc.). The special `any` type acts as a recovery/escape hatch — any
|
|
104
104
|
operation on it is silently permitted, preventing cascading errors.
|
|
105
105
|
|
|
106
|
-
### 4. Code Generator (`src/codegen
|
|
106
|
+
### 4. Code Generator (`src/codegen/`)
|
|
107
107
|
|
|
108
108
|
Walks the typed AST and emits clean, readable JavaScript. No intermediate representation
|
|
109
109
|
— the codegen writes directly to an output buffer with indentation tracking.
|
|
@@ -123,10 +123,11 @@ readable and debuggable — no name mangling, no opaque wrappers.
|
|
|
123
123
|
|
|
124
124
|
| Go | JavaScript | Notes |
|
|
125
125
|
|---|---|---|
|
|
126
|
-
| `struct` | ES6 `class` |
|
|
127
|
-
| Methods | Class instance methods | Receiver is `this` |
|
|
126
|
+
| `struct` | ES6 `class` | Positional constructor with zero-value defaults: `new Point(1, 2)`; a generated `__clone()` gives struct values Go copy semantics on assignment, call, return, `append`, and `range` |
|
|
127
|
+
| Methods | Class instance methods | Receiver is `this` (typed `*T` for pointer receivers) |
|
|
128
128
|
| Embedded structs | Flattened fields + delegation stubs | `Greet(...a) { return Base.prototype.Greet.call(this, ...a); }` |
|
|
129
|
-
| `[]T` (slice) | `Array` | `append` →
|
|
129
|
+
| `[]T` (slice) | `Array` | `append` → `__append`, `len` → `__len` |
|
|
130
|
+
| `[]float32`, `[]int8`…`[]int32`, `[]uint8`/`[]byte`…`[]uint32`, `[]rune` | TypedArray (`Float32Array`, `Int32Array`, `Uint8Array`, …) | `[]float64` and `[]int` stay plain `Array`; slicing → `.subarray` |
|
|
130
131
|
| `map[K]V` | Plain object `{}` | Key access via `[]`, iteration via `Object.entries()` |
|
|
131
132
|
| `nil` | `null` | |
|
|
132
133
|
| `error` | `__error` object | `error("msg")` → `__error("msg")`, `.Error()` → real method call. `toString()` for string context compat |
|
package/package.json
CHANGED
package/src/cli-core.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
basename,
|
|
17
17
|
dirname,
|
|
18
|
+
extname,
|
|
18
19
|
isAbsolute,
|
|
19
20
|
join,
|
|
20
21
|
relative,
|
|
@@ -29,6 +30,10 @@ import { generatePwa } from "./pwa.js";
|
|
|
29
30
|
import { runTests } from "./test-runner.js";
|
|
30
31
|
import { bundleVendor } from "./vendor.js";
|
|
31
32
|
|
|
33
|
+
const GOFRONT_VERSION = JSON.parse(
|
|
34
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
35
|
+
).version;
|
|
36
|
+
|
|
32
37
|
export function runCompile(inputPath, isDir, options) {
|
|
33
38
|
const {
|
|
34
39
|
sourceMap = false,
|
|
@@ -127,6 +132,7 @@ node_modules/
|
|
|
127
132
|
check: "gofront check",
|
|
128
133
|
},
|
|
129
134
|
dependencies: {},
|
|
135
|
+
devDependencies: { gofront: `^${GOFRONT_VERSION}` },
|
|
130
136
|
},
|
|
131
137
|
null,
|
|
132
138
|
2,
|
|
@@ -417,16 +423,21 @@ function isSubdirectory(parent, child) {
|
|
|
417
423
|
);
|
|
418
424
|
}
|
|
419
425
|
|
|
420
|
-
function cleanOutputDir(outDir,
|
|
426
|
+
function cleanOutputDir(outDir, project, srcDir) {
|
|
421
427
|
const resolvedOut = resolve(outDir);
|
|
422
|
-
const resolvedRoot = resolve(projectRoot);
|
|
423
|
-
const
|
|
428
|
+
const resolvedRoot = resolve(project.projectRoot);
|
|
429
|
+
const protectedDirs = [
|
|
430
|
+
resolvedRoot,
|
|
431
|
+
resolve(srcDir),
|
|
432
|
+
resolve(project.serveDir),
|
|
433
|
+
];
|
|
424
434
|
|
|
435
|
+
// Only ever delete a directory inside the project that holds no sources.
|
|
425
436
|
const isSafe =
|
|
426
|
-
resolvedOut
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
437
|
+
isSubdirectory(resolvedRoot, resolvedOut) &&
|
|
438
|
+
protectedDirs.every(
|
|
439
|
+
(p) => p !== resolvedOut && !isSubdirectory(resolvedOut, p),
|
|
440
|
+
);
|
|
430
441
|
|
|
431
442
|
if (isSafe && existsSync(resolvedOut)) {
|
|
432
443
|
rmSync(resolvedOut, { recursive: true, force: true });
|
|
@@ -434,7 +445,86 @@ function cleanOutputDir(outDir, projectRoot, srcDir) {
|
|
|
434
445
|
mkdirSync(resolvedOut, { recursive: true });
|
|
435
446
|
}
|
|
436
447
|
|
|
437
|
-
|
|
448
|
+
const STATIC_ASSET_EXTS = new Set([
|
|
449
|
+
".html",
|
|
450
|
+
".css",
|
|
451
|
+
".js",
|
|
452
|
+
".mjs",
|
|
453
|
+
".json",
|
|
454
|
+
".webmanifest",
|
|
455
|
+
".png",
|
|
456
|
+
".jpg",
|
|
457
|
+
".jpeg",
|
|
458
|
+
".gif",
|
|
459
|
+
".svg",
|
|
460
|
+
".webp",
|
|
461
|
+
".avif",
|
|
462
|
+
".ico",
|
|
463
|
+
".woff",
|
|
464
|
+
".woff2",
|
|
465
|
+
".ttf",
|
|
466
|
+
".otf",
|
|
467
|
+
".mp3",
|
|
468
|
+
".wav",
|
|
469
|
+
".ogg",
|
|
470
|
+
".mp4",
|
|
471
|
+
".webm",
|
|
472
|
+
".txt",
|
|
473
|
+
".xml",
|
|
474
|
+
".wasm",
|
|
475
|
+
]);
|
|
476
|
+
|
|
477
|
+
const ROOT_CONFIG_EXTS = new Set([".js", ".mjs", ".json"]);
|
|
478
|
+
|
|
479
|
+
// Whether a file in the served directory is a web asset worth copying to the build output.
|
|
480
|
+
function isStaticAsset(name, atProjectRoot) {
|
|
481
|
+
const ext = extname(name).toLowerCase();
|
|
482
|
+
if (!STATIC_ASSET_EXTS.has(ext)) return false;
|
|
483
|
+
// At the project root, .js/.json files are tooling config, not web assets.
|
|
484
|
+
if (atProjectRoot && ROOT_CONFIG_EXTS.has(ext))
|
|
485
|
+
return name === "manifest.json";
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Copies static web assets (css, images, fonts, ...) from the served directory into the build output.
|
|
490
|
+
function copyServeDirAssets(project, outDir, srcDir) {
|
|
491
|
+
const serveDir = resolve(project.serveDir);
|
|
492
|
+
const out = resolve(outDir);
|
|
493
|
+
if (!existsSync(serveDir) || serveDir === out) return 0;
|
|
494
|
+
const isRoot = serveDir === resolve(project.projectRoot);
|
|
495
|
+
const skipDirs = [out, resolve(srcDir)];
|
|
496
|
+
const devOutput = resolve(project.devOutputFile);
|
|
497
|
+
const skipFiles = new Set([devOutput, `${devOutput}.map`]);
|
|
498
|
+
let copied = 0;
|
|
499
|
+
|
|
500
|
+
const copyFile = (full) => {
|
|
501
|
+
const dest = join(out, relative(serveDir, full));
|
|
502
|
+
if (existsSync(dest)) return;
|
|
503
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
504
|
+
copyFileSync(full, dest);
|
|
505
|
+
copied++;
|
|
506
|
+
};
|
|
507
|
+
const walk = (dir) => {
|
|
508
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
509
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
510
|
+
const full = join(dir, entry.name);
|
|
511
|
+
if (entry.isDirectory()) {
|
|
512
|
+
if (!skipDirs.some((d) => d === full || isSubdirectory(d, full)))
|
|
513
|
+
walk(full);
|
|
514
|
+
} else if (
|
|
515
|
+
entry.isFile() &&
|
|
516
|
+
!skipFiles.has(full) &&
|
|
517
|
+
isStaticAsset(entry.name, isRoot)
|
|
518
|
+
) {
|
|
519
|
+
copyFile(full);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
walk(serveDir);
|
|
524
|
+
return copied;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function copyReleaseAssets(project, outDir, srcDir, assetConfig) {
|
|
438
528
|
const assetResult = copyAssets(project.projectRoot, assetConfig);
|
|
439
529
|
|
|
440
530
|
const candidates = [
|
|
@@ -449,6 +539,7 @@ function copyReleaseAssets(project, outDir, assetConfig) {
|
|
|
449
539
|
break;
|
|
450
540
|
}
|
|
451
541
|
}
|
|
542
|
+
copyServeDirAssets(project, outDir, srcDir);
|
|
452
543
|
return assetResult;
|
|
453
544
|
}
|
|
454
545
|
|
|
@@ -486,7 +577,7 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
486
577
|
const outDir = options.outDir ? resolve(options.outDir) : project.outDir;
|
|
487
578
|
const srcDir = options.srcDir ? resolve(options.srcDir) : project.srcDir;
|
|
488
579
|
|
|
489
|
-
cleanOutputDir(outDir, project
|
|
580
|
+
cleanOutputDir(outDir, project, srcDir);
|
|
490
581
|
|
|
491
582
|
const isDir = statSync(srcDir).isDirectory();
|
|
492
583
|
const outputFile = join(outDir, "app.js");
|
|
@@ -507,15 +598,20 @@ export async function handleBuild(targetDir = ".", options = {}) {
|
|
|
507
598
|
let vendor = null;
|
|
508
599
|
try {
|
|
509
600
|
vendor = await bundleVendor(project.projectRoot, {
|
|
510
|
-
dest: join(outDir, "vendor.js"),
|
|
511
601
|
minify: doMinify,
|
|
512
602
|
...options.vendorConfig,
|
|
603
|
+
dest: join(outDir, "vendor.js"),
|
|
513
604
|
});
|
|
514
605
|
} catch (e) {
|
|
515
606
|
console.warn(`gofront: vendor bundle warning: ${e.message}`);
|
|
516
607
|
}
|
|
517
608
|
|
|
518
|
-
const assets = copyReleaseAssets(
|
|
609
|
+
const assets = copyReleaseAssets(
|
|
610
|
+
project,
|
|
611
|
+
outDir,
|
|
612
|
+
srcDir,
|
|
613
|
+
options.assetConfig,
|
|
614
|
+
);
|
|
519
615
|
|
|
520
616
|
let pwa = null;
|
|
521
617
|
if (options.pwa) {
|
|
@@ -654,7 +750,11 @@ function setupDevWatchers(config) {
|
|
|
654
750
|
},
|
|
655
751
|
() => {
|
|
656
752
|
clearTimeout(htmlDebounce);
|
|
657
|
-
|
|
753
|
+
// Typed reload keeps a pending build error visible after the page reloads.
|
|
754
|
+
htmlDebounce = setTimeout(
|
|
755
|
+
() => config.devServer.notify({ type: "reload" }),
|
|
756
|
+
50,
|
|
757
|
+
);
|
|
658
758
|
},
|
|
659
759
|
);
|
|
660
760
|
if (serveWatcher) watchers.push(serveWatcher);
|
|
@@ -687,8 +787,11 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
687
787
|
|
|
688
788
|
copyAssets(project.projectRoot, options.assetConfig);
|
|
689
789
|
|
|
690
|
-
const devServer = createDevServer(serveDir, port, options);
|
|
691
790
|
const isDir = statSync(srcDir).isDirectory();
|
|
791
|
+
const devServer = createDevServer(serveDir, port, {
|
|
792
|
+
...options,
|
|
793
|
+
silent: true,
|
|
794
|
+
});
|
|
692
795
|
let initialError = null;
|
|
693
796
|
|
|
694
797
|
try {
|
|
@@ -700,6 +803,7 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
700
803
|
writeFileSync(outputFile, `${result.js}\n`);
|
|
701
804
|
} catch (err) {
|
|
702
805
|
initialError = err;
|
|
806
|
+
devServer.notifyError(err);
|
|
703
807
|
}
|
|
704
808
|
|
|
705
809
|
let watcherController = null;
|
|
@@ -721,6 +825,7 @@ export async function handleDev(targetDir = ".", options = {}) {
|
|
|
721
825
|
return {
|
|
722
826
|
devServer,
|
|
723
827
|
project,
|
|
828
|
+
port,
|
|
724
829
|
serveDir,
|
|
725
830
|
srcDir,
|
|
726
831
|
outputFile,
|