gofront 1.2.1 → 1.2.3
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 +1 -114
- package/package.json +1 -1
- package/src/codegen/index.js +15 -2
- package/src/codegen/statements.js +4 -1
- package/src/minifier.js +171 -41
package/README.md
CHANGED
|
@@ -387,16 +387,13 @@ gofront <dir> compile all *.go in directory → stdout
|
|
|
387
387
|
gofront <input> -o out.js write output to file (prints elapsed compile time e.g. "15ms")
|
|
388
388
|
gofront <input> --check type-check only
|
|
389
389
|
gofront <input> --watch watch for changes and recompile
|
|
390
|
-
gofront <input> -o out.js --serve watch + serve with live reload
|
|
390
|
+
gofront <input> -o out.js --serve watch + serve with live reload (default port 3000)
|
|
391
391
|
gofront <input> -o out.js --serve --port 8080 use a custom port
|
|
392
|
-
gofront <input> -o out.js --copy-assets compile and synchronize static assets from package.json assetCopy
|
|
393
392
|
gofront <input> --source-map append inline source map (single file or directory; multi-file packages emit per-file mappings)
|
|
394
393
|
gofront <input> --minify minify output (built-in minifier)
|
|
395
394
|
gofront <input> --minify --mangle minify and rename local identifiers
|
|
396
395
|
gofront <file.go> --ast dump AST (debug)
|
|
397
396
|
gofront <file.go> --tokens dump tokens (debug)
|
|
398
|
-
gofront test [dir] [--dom] run unit tests (-v verbose, -run <regex>)
|
|
399
|
-
gofront prep [dir] copy static assets and bundle vendor dependencies
|
|
400
397
|
gofront init [dir] scaffold a new project
|
|
401
398
|
gofront --version / -v print version
|
|
402
399
|
gofront --help / -h print this help
|
|
@@ -404,116 +401,6 @@ gofront --help / -h print this help
|
|
|
404
401
|
|
|
405
402
|
---
|
|
406
403
|
|
|
407
|
-
## Unit testing (`gofront test`)
|
|
408
|
-
|
|
409
|
-
GoFront provides native, Go-idiomatic unit testing out of the box:
|
|
410
|
-
|
|
411
|
-
- Files ending in `*_test.go` are automatically excluded from production builds (`gofront .`, `gofront -o app.js`) and compiled only when running tests.
|
|
412
|
-
- Built-in `testing` standard library package with `*testing.T`.
|
|
413
|
-
- Discover and execute `func TestXxx(t *testing.T)` functions with standard Go-style output.
|
|
414
|
-
|
|
415
|
-
### Writing a test
|
|
416
|
-
|
|
417
|
-
```go
|
|
418
|
-
package store
|
|
419
|
-
|
|
420
|
-
import "testing"
|
|
421
|
-
|
|
422
|
-
func TestAddTodo(t *testing.T) {
|
|
423
|
-
store := NewTodoStore()
|
|
424
|
-
store.Add("Buy milk")
|
|
425
|
-
|
|
426
|
-
if store.Count() != 1 {
|
|
427
|
-
t.Errorf("expected 1 item, got %d", store.Count())
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
Subtests and skips are fully supported:
|
|
433
|
-
|
|
434
|
-
```go
|
|
435
|
-
func TestFilter(t *testing.T) {
|
|
436
|
-
t.Run("active", func(t *testing.T) {
|
|
437
|
-
// subtest logic
|
|
438
|
-
})
|
|
439
|
-
t.Run("completed", func(t *testing.T) {
|
|
440
|
-
if testing.Short() {
|
|
441
|
-
t.Skip("skipping in short mode")
|
|
442
|
-
}
|
|
443
|
-
})
|
|
444
|
-
}
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Running tests
|
|
448
|
-
|
|
449
|
-
```sh
|
|
450
|
-
gofront test # run tests in current directory
|
|
451
|
-
gofront test ./src # run tests in specific directory
|
|
452
|
-
gofront test -v # verbose output (RUN, PASS/FAIL, logs)
|
|
453
|
-
gofront test -run "Todo" # run only tests matching regex pattern
|
|
454
|
-
gofront test --dom # initialize JSDOM for DOM/gom/templ tests
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
`--dom` requires `jsdom`. It is resolved from the project directory first and
|
|
458
|
-
then from GoFront's own installation, so a globally installed `gofront` works
|
|
459
|
-
without a project-local copy.
|
|
460
|
-
|
|
461
|
-
Only `TestXxx` functions declared in `*_test.go` files are discovered, matching Go.
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## Static assets & vendor bundling
|
|
466
|
-
|
|
467
|
-
GoFront includes native tooling to manage static assets and package external npm dependencies for the browser without requiring external wrappers or complex build tools.
|
|
468
|
-
|
|
469
|
-
### Asset copying (`assetCopy`)
|
|
470
|
-
|
|
471
|
-
Specify static assets (fonts, stylesheets, images) in `package.json` (or `gofront.json`):
|
|
472
|
-
|
|
473
|
-
```json
|
|
474
|
-
{
|
|
475
|
-
"assetCopy": [
|
|
476
|
-
{ "source": "node_modules/@fontsource/raleway/files", "dest": "app/fonts" },
|
|
477
|
-
{ "source": "node_modules/prismjs/themes", "dest": "app/css/prism-themes" }
|
|
478
|
-
]
|
|
479
|
-
}
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Run `gofront prep` to copy them, or pass `--copy-assets` to automatically synchronize assets on build.
|
|
483
|
-
|
|
484
|
-
### Vendor bundling (`gofront prep`)
|
|
485
|
-
|
|
486
|
-
Package external npm dependencies for the browser:
|
|
487
|
-
|
|
488
|
-
```sh
|
|
489
|
-
gofront prep
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
GoFront inspects your `dependencies`, dynamically uses `rolldown` or `esbuild` from your `devDependencies`, and bundles them into an ES module (defaulting to `app/vendor.js` or `vendor.js`).
|
|
493
|
-
|
|
494
|
-
Each bundled package is exposed on `window` under its full name, its unscoped name,
|
|
495
|
-
and a sanitised identifier (e.g. `fuse.js` → `window["fuse.js"]`, `window.fuse_js`).
|
|
496
|
-
To expose additional global names, add a `vendor.globals` mapping:
|
|
497
|
-
|
|
498
|
-
```json
|
|
499
|
-
{
|
|
500
|
-
"vendor": {
|
|
501
|
-
"dest": ["app/vendor.js", "public/vendor.js"],
|
|
502
|
-
"minify": true,
|
|
503
|
-
"globals": {
|
|
504
|
-
"fuse.js": ["Fuse"],
|
|
505
|
-
"prismjs": ["Prism"],
|
|
506
|
-
"@emailjs/browser": ["emailjs"]
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
`dest` accepts a string or an array (the bundle is written once and copied to each
|
|
513
|
-
extra destination). `gofront prep --minify` overrides `minify` from the command line.
|
|
514
|
-
|
|
515
|
-
---
|
|
516
|
-
|
|
517
404
|
## Multi-file packages
|
|
518
405
|
|
|
519
406
|
All `.go` files in a directory share the same namespace and are compiled as one unit.
|
package/package.json
CHANGED
package/src/codegen/index.js
CHANGED
|
@@ -333,10 +333,23 @@ export class CodeGen {
|
|
|
333
333
|
if (fields.length === 0) {
|
|
334
334
|
this.line("constructor() {}");
|
|
335
335
|
} else {
|
|
336
|
-
|
|
336
|
+
// A field named like a type used in a default (e.g. `Project Project`)
|
|
337
|
+
// would shadow that class inside the parameter scope, so alias it.
|
|
338
|
+
const binding = (f) => {
|
|
339
|
+
const re = new RegExp(`\\b${f.name}\\b`);
|
|
340
|
+
return fields.some((o) => re.test(o.zero)) ? `${f.name}$` : f.name;
|
|
341
|
+
};
|
|
342
|
+
const params = fields
|
|
343
|
+
.map((f) => {
|
|
344
|
+
const b = binding(f);
|
|
345
|
+
return b === f.name
|
|
346
|
+
? `${f.name} = ${f.zero}`
|
|
347
|
+
: `${f.name}: ${b} = ${f.zero}`;
|
|
348
|
+
})
|
|
349
|
+
.join(", ");
|
|
337
350
|
this.line(`constructor({ ${params} } = {}) {`);
|
|
338
351
|
this.indented(() => {
|
|
339
|
-
for (const f of fields) this.line(`this.${f.name} = ${f
|
|
352
|
+
for (const f of fields) this.line(`this.${f.name} = ${binding(f)};`);
|
|
340
353
|
});
|
|
341
354
|
this.line("}");
|
|
342
355
|
}
|
|
@@ -448,7 +448,10 @@ export const statementGenMethods = {
|
|
|
448
448
|
},
|
|
449
449
|
|
|
450
450
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
451
|
-
if (this._isMapRangeType(iterType))
|
|
451
|
+
if (this._isMapRangeType(iterType)) {
|
|
452
|
+
if (lhs.length === 1) return `Object.keys(${iteree})`;
|
|
453
|
+
return `Object.entries(${iteree})`;
|
|
454
|
+
}
|
|
452
455
|
if (this._isStringRangeType(iterType)) {
|
|
453
456
|
if (lhs.length === 1) return `Array.from(${iteree}).keys()`;
|
|
454
457
|
return `Array.from(${iteree}, (__c, __i) => [__i, __c.codePointAt(0)])`;
|
package/src/minifier.js
CHANGED
|
@@ -379,8 +379,7 @@ function needsSpaceBetween(left, right) {
|
|
|
379
379
|
return false;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
function
|
|
383
|
-
const tokens = tokenize(code);
|
|
382
|
+
function compressTokens(tokens) {
|
|
384
383
|
let out = "";
|
|
385
384
|
|
|
386
385
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -430,6 +429,10 @@ function compress(code) {
|
|
|
430
429
|
return out;
|
|
431
430
|
}
|
|
432
431
|
|
|
432
|
+
function compress(code) {
|
|
433
|
+
return compressTokens(tokenize(code));
|
|
434
|
+
}
|
|
435
|
+
|
|
433
436
|
function findPrevNonWs(tokens, index) {
|
|
434
437
|
for (let i = index - 1; i >= 0; i--) {
|
|
435
438
|
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
@@ -545,10 +548,18 @@ function _applyRenaming(tokens, renameMap) {
|
|
|
545
548
|
|
|
546
549
|
function mangle(code) {
|
|
547
550
|
const tokens = tokenize(code);
|
|
548
|
-
const nameGen = shortNameGeneratorFn();
|
|
549
|
-
const renameMap = new Map();
|
|
550
551
|
const { declaredLocals } = _collectLocalDecls(tokens);
|
|
551
552
|
_filterRenameCandidates(declaredLocals);
|
|
553
|
+
|
|
554
|
+
const occupied = new Set(MANGLE_GLOBALS);
|
|
555
|
+
for (const tok of tokens) {
|
|
556
|
+
if (tok.type === "ident" && !declaredLocals.has(tok.value)) {
|
|
557
|
+
occupied.add(tok.value);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const nameGen = shortNameGeneratorFn(occupied);
|
|
562
|
+
const renameMap = new Map();
|
|
552
563
|
for (const name of declaredLocals) renameMap.set(name, nameGen());
|
|
553
564
|
return _applyRenaming(tokens, renameMap);
|
|
554
565
|
}
|
|
@@ -606,61 +617,180 @@ function collectParams(tokens, openIdx, locals) {
|
|
|
606
617
|
}
|
|
607
618
|
}
|
|
608
619
|
|
|
609
|
-
function* shortNameGenerator() {
|
|
620
|
+
function* shortNameGenerator(occupied = new Set()) {
|
|
610
621
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
611
622
|
let index = 0;
|
|
612
623
|
while (true) {
|
|
624
|
+
let name;
|
|
613
625
|
if (index < 26) {
|
|
614
|
-
|
|
626
|
+
name = chars[index++];
|
|
615
627
|
} else {
|
|
616
628
|
const base = index - 26;
|
|
617
|
-
|
|
629
|
+
name = chars[base % 26] + (Math.floor(base / 26) + 1);
|
|
618
630
|
index++;
|
|
619
631
|
}
|
|
632
|
+
if (!occupied.has(name)) {
|
|
633
|
+
yield name;
|
|
634
|
+
}
|
|
620
635
|
}
|
|
621
636
|
}
|
|
622
637
|
|
|
623
638
|
// Make it callable (not an iterator interface)
|
|
624
|
-
function shortNameGeneratorFn() {
|
|
625
|
-
const gen = shortNameGenerator();
|
|
639
|
+
function shortNameGeneratorFn(occupied = new Set()) {
|
|
640
|
+
const gen = shortNameGenerator(occupied);
|
|
626
641
|
return () => gen.next().value;
|
|
627
642
|
}
|
|
628
643
|
|
|
629
644
|
// ── Stage 4: Literal folding ─────────────────────────────────
|
|
630
645
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
646
|
+
const FOLD_SAFE_PREV = new Set([
|
|
647
|
+
";",
|
|
648
|
+
"(",
|
|
649
|
+
"[",
|
|
650
|
+
"{",
|
|
651
|
+
",",
|
|
652
|
+
":",
|
|
653
|
+
"?",
|
|
654
|
+
"=",
|
|
655
|
+
"+=",
|
|
656
|
+
"-=",
|
|
657
|
+
"*=",
|
|
658
|
+
"/=",
|
|
659
|
+
"%=",
|
|
660
|
+
"&&",
|
|
661
|
+
"||",
|
|
662
|
+
"??",
|
|
663
|
+
"==",
|
|
664
|
+
"!=",
|
|
665
|
+
"===",
|
|
666
|
+
"!==",
|
|
667
|
+
"<",
|
|
668
|
+
"<=",
|
|
669
|
+
">",
|
|
670
|
+
">=",
|
|
671
|
+
"return",
|
|
672
|
+
"throw",
|
|
673
|
+
"case",
|
|
674
|
+
"yield",
|
|
675
|
+
"await",
|
|
676
|
+
"=>",
|
|
677
|
+
]);
|
|
678
|
+
|
|
679
|
+
function _foldBinaryOp(op, l, r) {
|
|
680
|
+
switch (op) {
|
|
681
|
+
case "+":
|
|
682
|
+
return l + r;
|
|
683
|
+
case "-":
|
|
684
|
+
return l - r;
|
|
685
|
+
case "*":
|
|
686
|
+
return l * r;
|
|
687
|
+
case "/":
|
|
688
|
+
return r !== 0 ? l / r : null;
|
|
689
|
+
case "%":
|
|
690
|
+
return r !== 0 ? l % r : null;
|
|
691
|
+
default:
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function _isHigherPrecedenceOp(op, afterOp) {
|
|
697
|
+
if (
|
|
698
|
+
afterOp === "**" ||
|
|
699
|
+
afterOp === "++" ||
|
|
700
|
+
afterOp === "--" ||
|
|
701
|
+
afterOp === "."
|
|
702
|
+
) {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
if (
|
|
706
|
+
(op === "+" || op === "-") &&
|
|
707
|
+
(afterOp === "*" || afterOp === "/" || afterOp === "%")
|
|
708
|
+
) {
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function _canFoldLeft(next) {
|
|
715
|
+
const prevTok = findPrevNonWs(next, next.length);
|
|
716
|
+
return !prevTok || FOLD_SAFE_PREV.has(prevTok.value);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function _evaluateFold(tok, opTok, rightTok) {
|
|
720
|
+
const l = Number(tok.value);
|
|
721
|
+
const r = Number(rightTok.value);
|
|
722
|
+
if (!Number.isFinite(l) || !Number.isFinite(r)) return null;
|
|
723
|
+
|
|
724
|
+
let val = _foldBinaryOp(opTok.value, l, r);
|
|
725
|
+
if (val === null || !Number.isFinite(val) || val < 0) return null;
|
|
726
|
+
if (Object.is(val, -0)) val = 0;
|
|
727
|
+
|
|
728
|
+
const s = String(val);
|
|
729
|
+
const origLen = tok.value.length + opTok.value.length + rightTok.value.length;
|
|
730
|
+
return s.length <= origLen ? s : null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function _tryFoldAt(current, i, next) {
|
|
734
|
+
const tok = current[i];
|
|
735
|
+
if (tok.type !== "num" || !_canFoldLeft(next)) return null;
|
|
736
|
+
|
|
737
|
+
const opInfo = nextNonWs(current, i);
|
|
738
|
+
if (opInfo?.tok.type !== "op") return null;
|
|
739
|
+
|
|
740
|
+
const op = opInfo.tok.value;
|
|
741
|
+
if (op !== "+" && op !== "-" && op !== "*" && op !== "/" && op !== "%") {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const rightInfo = nextNonWs(current, opInfo.idx);
|
|
746
|
+
if (rightInfo?.tok.type !== "num") return null;
|
|
747
|
+
|
|
748
|
+
const afterInfo = nextNonWs(current, rightInfo.idx);
|
|
749
|
+
if (
|
|
750
|
+
afterInfo?.tok.type === "op" &&
|
|
751
|
+
_isHigherPrecedenceOp(op, afterInfo.tok.value)
|
|
752
|
+
) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const folded = _evaluateFold(tok, opInfo.tok, rightInfo.tok);
|
|
757
|
+
if (!folded) return null;
|
|
758
|
+
|
|
759
|
+
next.push({ type: "num", value: folded });
|
|
760
|
+
return rightInfo.idx + 1;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function foldTokens(tokens) {
|
|
764
|
+
let changed = true;
|
|
765
|
+
let current = tokens;
|
|
766
|
+
let iterations = 0;
|
|
767
|
+
|
|
768
|
+
while (changed && iterations < 10) {
|
|
769
|
+
changed = false;
|
|
770
|
+
iterations++;
|
|
771
|
+
const next = [];
|
|
772
|
+
let i = 0;
|
|
773
|
+
|
|
774
|
+
while (i < current.length) {
|
|
775
|
+
const nextIdx = _tryFoldAt(current, i, next);
|
|
776
|
+
if (nextIdx !== null) {
|
|
777
|
+
i = nextIdx;
|
|
778
|
+
changed = true;
|
|
779
|
+
} else {
|
|
780
|
+
next.push(current[i]);
|
|
781
|
+
i++;
|
|
657
782
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
current = next;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return current;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function foldLiterals(code) {
|
|
792
|
+
const tokens = tokenize(code);
|
|
793
|
+
return compressTokens(foldTokens(tokens));
|
|
664
794
|
}
|
|
665
795
|
|
|
666
796
|
// ── Public API ───────────────────────────────────────────────
|