gofront 1.3.12 → 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.
Files changed (63) hide show
  1. package/README.md +90 -20
  2. package/package.json +2 -1
  3. package/src/{codegen → backend/js}/expressions.js +217 -60
  4. package/src/{codegen → backend/js}/index.js +47 -36
  5. package/src/{codegen → backend/js}/runtime.js +11 -0
  6. package/src/{codegen → backend/js}/statements.js +43 -48
  7. package/src/backend/js/stdlib/bits.js +32 -0
  8. package/src/{codegen → backend/js}/stdlib/index.js +3 -0
  9. package/src/{codegen → backend/js}/templ.js +31 -2
  10. package/src/backend/wasm/boundary.js +1345 -0
  11. package/src/backend/wasm/emit.js +5779 -0
  12. package/src/backend/wasm/encode.js +789 -0
  13. package/src/backend/wasm/glue.js +130 -0
  14. package/src/backend/wasm/index.js +1413 -0
  15. package/src/backend/wasm/types.js +420 -0
  16. package/src/backend/wasm/wat.js +255 -0
  17. package/src/cli-core.js +52 -7
  18. package/src/compiler.js +328 -12
  19. package/src/dev-server.js +56 -15
  20. package/src/index.js +17 -9
  21. package/src/lexer.js +18 -0
  22. package/src/lower/boxing.js +36 -0
  23. package/src/lower/captures.js +179 -0
  24. package/src/lower/embedding.js +30 -0
  25. package/src/lower/escape.js +169 -0
  26. package/src/lower/functions.js +30 -0
  27. package/src/lower/globals.js +324 -0
  28. package/src/lower/index.js +115 -0
  29. package/src/lower/ownership.js +142 -0
  30. package/src/lower/range.js +70 -0
  31. package/src/minifier.js +2 -0
  32. package/src/parser/index.js +8 -3
  33. package/src/resolver.js +4 -1
  34. package/src/templ-lexer.js +107 -3
  35. package/src/templ-parser.js +29 -0
  36. package/src/test-runner.js +74 -20
  37. package/src/typechecker/expressions.js +40 -1
  38. package/src/typechecker/index.js +301 -6
  39. package/src/typechecker/statements.js +7 -0
  40. package/src/typechecker/stdlib/core.js +17 -0
  41. package/src/typechecker/types.js +56 -2
  42. /package/src/{codegen → backend/js}/source-map.js +0 -0
  43. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  44. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  45. /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
  46. /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
  47. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  48. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  49. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  50. /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
  51. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  52. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  53. /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
  54. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  55. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  56. /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
  57. /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
  58. /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
  59. /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
  60. /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
  61. /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
  62. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  63. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -1,6 +1,6 @@
1
1
  // Code generator: walks the typed AST and emits clean JavaScript.
2
2
  //
3
- // Split into sub-modules under codegen/:
3
+ // Split into sub-modules under backend/js/:
4
4
  // source-map.js — VLQ encoder and source map builder
5
5
  // statements.js — genBlock, genStmt, genFor, genSwitch, etc.
6
6
  // expressions.js — genExpr, genCall, genCompositeLit, helpers
@@ -17,7 +17,12 @@
17
17
  // - make(map[K]V) → {}
18
18
  // - for range → for...of with .entries()
19
19
 
20
- import { isComplex, isNumeric } from "../typechecker/types.js";
20
+ import {
21
+ computeEmbeddedStubs,
22
+ isReferenceType,
23
+ scanAddressTaken,
24
+ } from "../../lower/index.js";
25
+ import { isComplex, isNumeric } from "../../typechecker/types.js";
21
26
  import { expressionGenMethods } from "./expressions.js";
22
27
  import {
23
28
  HELPER_APPEND,
@@ -27,6 +32,7 @@ import {
27
32
  HELPER_ERROR,
28
33
  HELPER_ERROR_IS,
29
34
  HELPER_IFACE_BOX,
35
+ HELPER_INJECT_STYLES,
30
36
  HELPER_LEN,
31
37
  HELPER_PATH_CLEAN,
32
38
  HELPER_S,
@@ -42,6 +48,8 @@ import { statementGenMethods } from "./statements.js";
42
48
  import { stdlibGenMethods } from "./stdlib/index.js";
43
49
  import { templGenMethods } from "./templ.js";
44
50
 
51
+ export { buildSourceMap };
52
+
45
53
  // Valid Go identifiers that cannot be used as JS bindings; emitted with a `$` suffix.
46
54
  const JS_RESERVED = new Set([
47
55
  "arguments",
@@ -90,13 +98,17 @@ export class CodeGen {
90
98
  checker = null,
91
99
  jsImports = new Map(),
92
100
  bundledPackages = new Set(),
101
+ options = {},
93
102
  ) {
94
103
  this.checker = checker;
104
+ this.target = options.target ?? "js";
105
+ this.strict = Boolean(options.strict || this.target === "both");
95
106
  this.out = [];
96
107
  this.indent = 0;
97
108
  this.structNames = new Set();
98
109
  this.structFields = new Map();
99
110
  this.namedWrapperNames = new Set();
111
+ this.collectedCss = [];
100
112
  this.jsImports = jsImports;
101
113
  this.bundledPackages = bundledPackages;
102
114
  this.namedReturnVars = null; // names of current function's named return vars
@@ -191,6 +203,10 @@ export class CodeGen {
191
203
  }
192
204
 
193
205
  _callInitAndMain(initNames, program, isTest = false) {
206
+ if (this.collectedCss.length > 0) {
207
+ const uniqueCss = [...new Set(this.collectedCss)];
208
+ this.line(`__injectStyles(${JSON.stringify(uniqueCss.join("\n\n"))});`);
209
+ }
194
210
  for (const name of initNames) this.line(`${name}();`);
195
211
  if (
196
212
  !isTest &&
@@ -201,6 +217,14 @@ export class CodeGen {
201
217
 
202
218
  generate(program, options = {}) {
203
219
  const isTest = options.isTest ?? false;
220
+ // Ensure _pkgName is set on all decls (generateAll sets it for
221
+ // multi-file; for single-file compiles we derive it from the AST).
222
+ const pkgName = program.pkg?.name;
223
+ if (pkgName) {
224
+ for (const d of program.decls) {
225
+ if (!d._pkgName) d._pkgName = pkgName;
226
+ }
227
+ }
204
228
  this._renameReservedIdents(program.decls);
205
229
  const methods = this._collectDecls(program);
206
230
  this._emitJsImports();
@@ -227,7 +251,7 @@ export class CodeGen {
227
251
  return this.out.join("\n");
228
252
  }
229
253
 
230
- // Emits FuncDecl and TemplDecl nodes; returns renamed init function names.
254
+ // Emits FuncDecl, TemplDecl, and CssDecl nodes; returns renamed init function names.
231
255
  _emitFuncDecls(program) {
232
256
  let initCount = 0;
233
257
  const initNames = [];
@@ -247,6 +271,10 @@ export class CodeGen {
247
271
  this._currentSrcFileIdx = d._srcFileIdx ?? 0;
248
272
  this.genTemplDecl(d);
249
273
  this.blank();
274
+ } else if (d.kind === "CssDecl") {
275
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
276
+ this.genCssDecl(d);
277
+ this.blank();
250
278
  }
251
279
  }
252
280
  return initNames;
@@ -306,6 +334,7 @@ export class CodeGen {
306
334
  MethodDecl: "_renameMethodDeclNode",
307
335
  FuncLit: "_renameSignature",
308
336
  TemplDecl: "_renameSignature",
337
+ CssDecl: "_renameSignature",
309
338
  VarDecl: "_renameDeclSpecs",
310
339
  ConstDecl: "_renameDeclSpecs",
311
340
  TypeSwitchStmt: "_renameTypeSwitchNode",
@@ -381,6 +410,7 @@ export class CodeGen {
381
410
  _prependHelpers(isTest = false) {
382
411
  const needsTesting = isTest || this._usesTesting;
383
412
  const HELPER_MAP = [
413
+ [this.collectedCss.length > 0, HELPER_INJECT_STYLES],
384
414
  [this._usesLen, HELPER_LEN],
385
415
  [this._usesAppend, HELPER_APPEND],
386
416
  [this._usesSliceGuard, HELPER_S],
@@ -414,12 +444,17 @@ export class CodeGen {
414
444
  for (let i = 0; i < programs.length; i++) {
415
445
  for (const decl of programs[i].decls) {
416
446
  decl._srcFileIdx = i;
447
+ decl._pkgName = programs[i].pkg?.name;
417
448
  }
418
449
  }
419
450
  const merged = { decls: programs.flatMap((p) => p.decls) };
420
451
  return this.generate(merged, options);
421
452
  }
422
453
 
454
+ getCss() {
455
+ return [...new Set(this.collectedCss)].join("\n\n");
456
+ }
457
+
423
458
  getMappings() {
424
459
  return this._srcMappings;
425
460
  }
@@ -596,12 +631,13 @@ export class CodeGen {
596
631
  }
597
632
 
598
633
  _genEmbeddedMethodStubs(name, methodDecls) {
599
- if (!this.checker) return;
600
- const resolvedType = this.checker.types.get(name)?.underlying;
601
- if (resolvedType?.kind !== "struct" || !resolvedType._embeds) return;
602
- const declared = new Set(methodDecls.map((m) => m.name));
603
- for (const embed of resolvedType._embeds)
604
- this._genSingleEmbedStubs(embed, declared);
634
+ const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
635
+ for (const stub of stubs) {
636
+ this.blank();
637
+ this.line(
638
+ `${stub.methodName}(...__a) { return ${stub.embedName}.prototype.${stub.methodName}.call(this, ...__a); }`,
639
+ );
640
+ }
605
641
  }
606
642
 
607
643
  genMethod(decl, recvField = null) {
@@ -742,36 +778,11 @@ export class CodeGen {
742
778
 
743
779
  // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
744
780
  _scanAddressTaken(node) {
745
- if (!node || typeof node !== "object") return;
746
- if (Array.isArray(node)) {
747
- for (const child of node) this._scanAddressTaken(child);
748
- return;
749
- }
750
- // &x — the operand ident will have _addressTaken set by typechecker
751
- if (node.kind === "Ident" && node._addressTaken) {
752
- // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
753
- const t = node._type;
754
- if (t && !this._isReferenceType(t)) {
755
- this._boxedVars.add(node.name);
756
- }
757
- }
758
- // Recurse into FuncLit too — closures may take address of outer vars
759
- for (const key of Object.keys(node)) {
760
- if (key.startsWith("_")) continue;
761
- this._scanAddressTaken(node[key]);
762
- }
781
+ scanAddressTaken(node, this._boxedVars);
763
782
  }
764
783
 
765
784
  _isReferenceType(t) {
766
- if (!t) return false;
767
- const base = t.kind === "named" ? t.underlying : t;
768
- return (
769
- base?.kind === "struct" ||
770
- base?.kind === "slice" ||
771
- base?.kind === "map" ||
772
- base?.kind === "func" ||
773
- base?.kind === "interface"
774
- );
785
+ return isReferenceType(t);
775
786
  }
776
787
 
777
788
  // ── Variable / const declarations ────────────────────────────
@@ -312,3 +312,14 @@ var __GoFront_T = __GoFront_T || class {
312
312
  }
313
313
  };
314
314
  var __testing_T = __GoFront_T;`;
315
+
316
+ export const HELPER_INJECT_STYLES = `var __injectStyles = __injectStyles || function(css) {
317
+ if (typeof document === "undefined" || !document || !document.head || !css) return;
318
+ let s = document.getElementById("gofront-styles");
319
+ if (!s) {
320
+ s = document.createElement("style");
321
+ s.id = "gofront-styles";
322
+ document.head.appendChild(s);
323
+ }
324
+ s.textContent += "\\n" + css;
325
+ };`;
@@ -1,6 +1,13 @@
1
- // CodeGen statement methods — installed as a mixin on CodeGen.prototype.
2
-
3
- import { isComplex, isNumeric } from "../typechecker/types.js";
1
+ import {
2
+ bodyRedeclares,
3
+ isIntRangeType,
4
+ isMapRangeType,
5
+ isRangeFor,
6
+ isSimpleIntRange,
7
+ isStringRangeType,
8
+ nodeAssigns,
9
+ } from "../../lower/index.js";
10
+ import { isComplex } from "../../typechecker/types.js";
4
11
 
5
12
  /** @typedef {import('./index.js').CodeGen} CodeGen */
6
13
 
@@ -55,9 +62,20 @@ export const statementGenMethods = {
55
62
  case "TypeDecl":
56
63
  this.genTypeDeclWithMethods(stmt, []);
57
64
  break;
58
- case "IncDecStmt":
59
- this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
65
+ case "IncDecStmt": {
66
+ if (
67
+ this.strict &&
68
+ this._genStrictCompound(
69
+ stmt.expr,
70
+ stmt.op === "++" ? "+" : "-",
71
+ "1",
72
+ srcLine,
73
+ )
74
+ )
75
+ break;
76
+ this.line(`${this.genExpr(stmt.expr)}${stmt.op};`, srcLine);
60
77
  break;
78
+ }
61
79
  case "ExprStmt":
62
80
  this.line(`${this.genExpr(stmt.expr)};`);
63
81
  break;
@@ -248,6 +266,19 @@ export const statementGenMethods = {
248
266
  if (active.length === 0) {
249
267
  if (rhs.length > 0) this.line(`${rhs[0]};`);
250
268
  } else if (lhs.length === 1) {
269
+ if (
270
+ this.strict &&
271
+ stmt.op !== "=" &&
272
+ this._genStrictCompound(
273
+ stmt.lhs[0],
274
+ stmt.op.slice(0, -1),
275
+ active[0].r,
276
+ null,
277
+ active[0].l,
278
+ )
279
+ ) {
280
+ return;
281
+ }
251
282
  this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
252
283
  } else {
253
284
  this._genAssignMulti(stmt, lhs, rhs, active, pairs);
@@ -449,28 +480,19 @@ export const statementGenMethods = {
449
480
  },
450
481
 
451
482
  isRangeFor(stmt) {
452
- if (!stmt.init) return false;
453
- const init = stmt.init;
454
- if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
455
- return init.rhs?.[0]?.kind === "RangeExpr";
483
+ return isRangeFor(stmt);
456
484
  },
457
485
 
458
486
  _isIntRangeType(iterType) {
459
- return isNumeric(iterType);
487
+ return isIntRangeType(iterType);
460
488
  },
461
489
 
462
490
  _isMapRangeType(iterType) {
463
- return (
464
- iterType?.kind === "map" ||
465
- (iterType?.kind === "named" && iterType.underlying?.kind === "map")
466
- );
491
+ return isMapRangeType(iterType);
467
492
  },
468
493
 
469
494
  _isStringRangeType(iterType) {
470
- return (
471
- (iterType?.kind === "basic" && iterType.name === "string") ||
472
- (iterType?.kind === "untyped" && iterType.base === "string")
473
- );
495
+ return isStringRangeType(iterType);
474
496
  },
475
497
 
476
498
  _rangeVarTarget(e, isAssign) {
@@ -498,17 +520,7 @@ export const statementGenMethods = {
498
520
 
499
521
  // True when the body re-declares one of `names` at its top level (legal Go shadowing).
500
522
  _bodyRedeclares(body, names) {
501
- if (names.length === 0) return false;
502
- for (const s of body.stmts ?? []) {
503
- if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
504
- return true;
505
- if (
506
- (s.kind === "VarDecl" || s.kind === "ConstDecl") &&
507
- s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
508
- )
509
- return true;
510
- }
511
- return false;
523
+ return bodyRedeclares(body, names);
512
524
  },
513
525
 
514
526
  // Range value variable: aliases the element unless the body writes the variable or the collection.
@@ -565,9 +577,7 @@ export const statementGenMethods = {
565
577
 
566
578
  // Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
567
579
  _isSimpleIntRange(stmt, name, isAssign) {
568
- if (isAssign) return false;
569
- if (!name || name === "_") return true;
570
- return !this._boxedVars.has(name) && !this._nodeAssigns(stmt.body, name);
580
+ return isSimpleIntRange(stmt, name, isAssign, this._boxedVars);
571
581
  },
572
582
 
573
583
  _genIntRangeBody(stmt, name, isAssign) {
@@ -603,22 +613,7 @@ export const statementGenMethods = {
603
613
 
604
614
  // True when `node` contains an assignment or ++/-- targeting identifier `name`.
605
615
  _nodeAssigns(node, name) {
606
- if (!node || typeof node !== "object") return false;
607
- if (Array.isArray(node))
608
- return node.some((n) => this._nodeAssigns(n, name));
609
- if (
610
- (node.kind === "AssignStmt" &&
611
- node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
612
- (node.kind === "IncDecStmt" &&
613
- node.expr.kind === "Ident" &&
614
- node.expr.name === name)
615
- )
616
- return true;
617
- for (const key of Object.keys(node)) {
618
- if (key.startsWith("_")) continue;
619
- if (this._nodeAssigns(node[key], name)) return true;
620
- }
621
- return false;
616
+ return nodeAssigns(node, name);
622
617
  },
623
618
 
624
619
  _genIterFor(init, iterType, iteree, lhs, body) {
@@ -0,0 +1,32 @@
1
+ // src/backend/js/stdlib/bits.js
2
+ // CodeGen for Go `math/bits` package.
3
+
4
+ /** @typedef {import('../index.js').CodeGen} CodeGen */
5
+
6
+ const BITS_MAP = {
7
+ LeadingZeros32: ([x]) => `Math.clz32((${x}) >>> 0)`,
8
+ TrailingZeros32: ([x]) =>
9
+ `((() => { const v = (${x}) >>> 0; return v === 0 ? 32 : 31 - Math.clz32(v & -v); })())`,
10
+ OnesCount32: ([x]) =>
11
+ `((() => { let v = (${x}) >>> 0; v = v - ((v >>> 1) & 0x55555555); v = (v & 0x33333333) + ((v >>> 2) & 0x33333333); return (Math.imul((v + (v >>> 4)) & 0x0f0f0f0f, 0x01010101)) >>> 24; })())`,
12
+ RotateLeft32: ([x, k]) =>
13
+ `((() => { const s = (${k}) & 31; const v = (${x}) >>> 0; return ((v << s) | (v >>> ((32 - s) & 31))) >>> 0; })())`,
14
+
15
+ LeadingZeros64: ([x]) =>
16
+ `((() => { const v = BigInt.asUintN(64, BigInt(${x})); if (v === 0n) return 64; const hi = Number(v >> 32n); return hi !== 0 ? Math.clz32(hi) : 32 + Math.clz32(Number(v & 0xffffffffn)); })())`,
17
+ TrailingZeros64: ([x]) =>
18
+ `((() => { const v = BigInt.asUintN(64, BigInt(${x})); if (v === 0n) return 64; const lo = Number(v & 0xffffffffn); if (lo !== 0) return 31 - Math.clz32(lo & -lo); const hi = Number((v >> 32n) & 0xffffffffn); return 32 + (31 - Math.clz32(hi & -hi)); })())`,
19
+ OnesCount64: ([x]) =>
20
+ `((() => { let v = BigInt.asUintN(64, BigInt(${x})); let c = 0; while (v > 0n) { if (v & 1n) c++; v >>= 1n; } return c; })())`,
21
+ RotateLeft64: ([x, k]) =>
22
+ `((() => { const s = BigInt(${k}) & 63n; const v = BigInt.asUintN(64, BigInt(${x})); return BigInt.asUintN(64, (v << s) | (v >> ((64n - s) & 63n))); })())`,
23
+ };
24
+
25
+ /** @type {ThisType<CodeGen>} */
26
+ export const bitsMethods = {
27
+ _genBits(fn, a) {
28
+ const args = a();
29
+ const gen = BITS_MAP[fn];
30
+ return gen ? gen(args) : undefined;
31
+ },
32
+ };
@@ -1,6 +1,7 @@
1
1
  // CodeGen stdlib call generation — installed as a mixin on CodeGen.prototype.
2
2
  // One file per Go package under ./stdlib/. This index merges them.
3
3
 
4
+ import { bitsMethods } from "./bits.js";
4
5
  import { builderMethods } from "./builder.js";
5
6
  import { bytesMethods } from "./bytes.js";
6
7
  import { errorsMethods } from "./errors.js";
@@ -34,6 +35,7 @@ const STDLIB_METHOD_MAP = {
34
35
  strconv: "_genStrconv",
35
36
  sort: "_genSort",
36
37
  math: "_genMath",
38
+ bits: "_genBits",
37
39
  unicode: "_genUnicode",
38
40
  os: "_genOs",
39
41
  errors: "_genErrors",
@@ -76,6 +78,7 @@ const dispatchMethods = {
76
78
 
77
79
  /** @type {ThisType<CodeGen>} */
78
80
  export const stdlibGenMethods = {
81
+ ...bitsMethods,
79
82
  ...builderMethods,
80
83
  ...bytesMethods,
81
84
  ...errorsMethods,
@@ -2,8 +2,8 @@
2
2
  // Mounts an HTML/expression tree onto a parent DOM node by emitting plain
3
3
  // document.createElement / appendChild / setAttribute calls.
4
4
 
5
- import { Parser } from "../parser/index.js";
6
- import { T, Token } from "../tokens.js";
5
+ import { Parser } from "../../parser/index.js";
6
+ import { T, Token } from "../../tokens.js";
7
7
 
8
8
  /** @typedef {import('./index.js').CodeGen} CodeGen */
9
9
 
@@ -26,6 +26,35 @@ export const templGenMethods = {
26
26
  this.line("}");
27
27
  },
28
28
 
29
+ genCssDecl(decl) {
30
+ if (decl.params && decl.params.length > 0) {
31
+ throw new Error(
32
+ `css ${decl.name}: parameterized css declarations are not yet supported`,
33
+ );
34
+ }
35
+ const className = this.getCssClassName(decl);
36
+ const scopedCss = `.${className} {\n${decl.cssText.trim()}\n}`;
37
+ this.collectedCss.push(scopedCss);
38
+
39
+ this.line(`function ${decl.name}() {`);
40
+ this.indented(() => {
41
+ this.line(`return ${JSON.stringify(className)};`);
42
+ });
43
+ this.line("}");
44
+ },
45
+
46
+ getCssClassName(decl) {
47
+ if (decl._className) return decl._className;
48
+ let h = 2166136261 >>> 0;
49
+ const str = `${decl._pkgName || ""}_${decl.name}_${decl.cssText}`;
50
+ for (let i = 0; i < str.length; i++) {
51
+ h = Math.imul(h ^ str.charCodeAt(i), 16777619) >>> 0;
52
+ }
53
+ const hash = (h >>> 0).toString(36);
54
+ decl._className = `gfc_${decl.name}_${hash}`;
55
+ return decl._className;
56
+ },
57
+
29
58
  // Emit mount statements for an array of TemplNodes into parent variable `p`.
30
59
  _genTemplNodes(nodes, p) {
31
60
  if (!nodes || nodes.length === 0) return;