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 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.js`)
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.js`)
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.js`)
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` | Single destructured-object constructor: `new Point({ X: 1, Y: 2 })` |
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` → spread, `len` → `.length` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gofront",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "author": "Luuk van Venrooij",
5
5
  "license": "MIT",
6
6
  "repository": {
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, projectRoot, srcDir) {
426
+ function cleanOutputDir(outDir, project, srcDir) {
421
427
  const resolvedOut = resolve(outDir);
422
- const resolvedRoot = resolve(projectRoot);
423
- const resolvedSrc = resolve(srcDir);
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 !== resolvedRoot &&
427
- resolvedOut !== resolvedSrc &&
428
- !isSubdirectory(resolvedOut, resolvedRoot) &&
429
- !isSubdirectory(resolvedOut, resolvedSrc);
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
- function copyReleaseAssets(project, outDir, assetConfig) {
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.projectRoot, srcDir);
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(project, outDir, options.assetConfig);
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
- htmlDebounce = setTimeout(() => config.devServer.notify(), 50);
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,