@barefootjs/mojolicious 0.6.0 → 0.7.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.
@@ -1,4 +1,5 @@
1
1
  // src/adapter/mojo-adapter.ts
2
+ import ts from "typescript";
2
3
  import {
3
4
  BaseAdapter,
4
5
  isBooleanAttr,
@@ -80,6 +81,22 @@ function resolveJsxChildrenProp(props) {
80
81
  return [];
81
82
  return prop.value.children;
82
83
  }
84
+ function parsePureStringLiteral(source) {
85
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
86
+ const stmt = sf.statements[0];
87
+ if (!stmt || !ts.isVariableStatement(stmt))
88
+ return null;
89
+ const decl = stmt.declarationList.declarations[0];
90
+ let init = decl?.initializer;
91
+ while (init && ts.isParenthesizedExpression(init))
92
+ init = init.expression;
93
+ if (!init)
94
+ return null;
95
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
96
+ return init.text;
97
+ }
98
+ return null;
99
+ }
83
100
 
84
101
  class MojoAdapter extends BaseAdapter {
85
102
  name = "mojolicious";
@@ -94,6 +111,8 @@ class MojoAdapter extends BaseAdapter {
94
111
  propsObjectName = null;
95
112
  propsParams = [];
96
113
  stringValueNames = new Set;
114
+ moduleStringConsts = new Map;
115
+ loopBoundNames = new Map;
97
116
  constructor(options = {}) {
98
117
  super();
99
118
  this.options = {
@@ -115,6 +134,8 @@ class MojoAdapter extends BaseAdapter {
115
134
  if (isStringTypeInfo(p.type))
116
135
  this.stringValueNames.add(p.name);
117
136
  }
137
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
138
+ this.loopBoundNames.clear();
118
139
  this.errors = [];
119
140
  this.childrenCaptureCounter = 0;
120
141
  if (!options?.siblingTemplatesRegistered) {
@@ -139,6 +160,27 @@ class MojoAdapter extends BaseAdapter {
139
160
  extension: this.extension
140
161
  };
141
162
  }
163
+ collectModuleStringConsts(constants) {
164
+ const map = new Map;
165
+ for (const c of constants ?? []) {
166
+ if (!c.isModule)
167
+ continue;
168
+ if (c.value === undefined)
169
+ continue;
170
+ const literal = parsePureStringLiteral(c.value);
171
+ if (literal !== null)
172
+ map.set(c.name, literal);
173
+ }
174
+ return map;
175
+ }
176
+ resolveModuleStringConst(name) {
177
+ if (this.loopBoundNames.has(name))
178
+ return null;
179
+ const value = this.moduleStringConsts.get(name);
180
+ if (value === undefined)
181
+ return null;
182
+ return `'${value.replace(/[\\']/g, (m) => `\\${m}`)}'`;
183
+ }
142
184
  generateScriptRegistrations(ir, scriptBaseName) {
143
185
  const hasInteractivity = this.hasClientInteractivity(ir);
144
186
  if (!hasInteractivity)
@@ -405,6 +447,10 @@ ${whenTrue}
405
447
  }
406
448
  const param = loop.param;
407
449
  const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
450
+ const loopBound = loop.iterationShape === "keys" ? [param] : [param, loop.index ?? "_i"];
451
+ for (const n of loopBound) {
452
+ this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
453
+ }
408
454
  const prevInLoop = this.inLoop;
409
455
  this.inLoop = true;
410
456
  const renderedChildren = this.renderChildren(loop.children);
@@ -438,6 +484,13 @@ ${renderedChildren}` : renderedChildren;
438
484
  } else {
439
485
  lines.push(children);
440
486
  }
487
+ for (const n of loopBound) {
488
+ const c = (this.loopBoundNames.get(n) ?? 1) - 1;
489
+ if (c <= 0)
490
+ this.loopBoundNames.delete(n);
491
+ else
492
+ this.loopBoundNames.set(n, c);
493
+ }
441
494
  lines.push(`% }`);
442
495
  lines.push(`<%== bf->comment("/loop:${loop.markerId}") %>`);
443
496
  return lines.join(`
@@ -884,6 +937,25 @@ function renderSortMethod(recv, c) {
884
937
  });
885
938
  return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
886
939
  }
940
+ function renderReduceMethod(recv, op, direction) {
941
+ const keyEntry = op.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${op.key.field}'`;
942
+ const init = op.type === "string" ? `'${op.init.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` : op.init;
943
+ return `bf->reduce(${recv}, { op => '${op.op}', ${keyEntry}, type => '${op.type}', init => ${init}, direction => '${direction}' })`;
944
+ }
945
+ function renderFlatMethod(recv, depth) {
946
+ const d = depth === "infinity" ? -1 : depth;
947
+ return `bf->flat(${recv}, ${d})`;
948
+ }
949
+ function renderFlatMapMethod(recv, op) {
950
+ const proj = op.projection;
951
+ if (proj.kind === "tuple") {
952
+ const specs = proj.elements.map((l) => l.kind === "self" ? `['self', '']` : `['field', '${l.field}']`).join(", ");
953
+ return `bf->flat_map_tuple(${recv}, ${specs})`;
954
+ }
955
+ if (proj.kind === "self")
956
+ return `bf->flat_map(${recv}, 'self', '')`;
957
+ return `bf->flat_map(${recv}, 'field', '${proj.field}')`;
958
+ }
887
959
  function isStringTypeInfo(type) {
888
960
  return type?.kind === "primitive" && type.primitive === "string";
889
961
  }
@@ -1008,6 +1080,15 @@ class MojoFilterEmitter {
1008
1080
  sortMethod(_method, object, comparator, emit) {
1009
1081
  return renderSortMethod(emit(object), comparator);
1010
1082
  }
1083
+ reduceMethod(method, object, reduceOp, emit) {
1084
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1085
+ }
1086
+ flatMethod(object, depth, emit) {
1087
+ return renderFlatMethod(emit(object), depth);
1088
+ }
1089
+ flatMapMethod(object, op, emit) {
1090
+ return renderFlatMapMethod(emit(object), op);
1091
+ }
1011
1092
  conditional(_test, _consequent, _alternate) {
1012
1093
  return "1";
1013
1094
  }
@@ -1028,6 +1109,9 @@ class MojoTopLevelEmitter {
1028
1109
  this.adapter = adapter;
1029
1110
  }
1030
1111
  identifier(name) {
1112
+ const inlined = this.adapter.resolveModuleStringConst(name);
1113
+ if (inlined !== null)
1114
+ return inlined;
1031
1115
  return `$${name}`;
1032
1116
  }
1033
1117
  literal(value, literalType) {
@@ -1129,6 +1213,15 @@ class MojoTopLevelEmitter {
1129
1213
  sortMethod(_method, object, comparator, emit) {
1130
1214
  return renderSortMethod(emit(object), comparator);
1131
1215
  }
1216
+ reduceMethod(method, object, reduceOp, emit) {
1217
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1218
+ }
1219
+ flatMethod(object, depth, emit) {
1220
+ return renderFlatMethod(emit(object), depth);
1221
+ }
1222
+ flatMapMethod(object, op, emit) {
1223
+ return renderFlatMapMethod(emit(object), op);
1224
+ }
1132
1225
  conditional(test, consequent, alternate, emit) {
1133
1226
  return `(${emit(test)} ? ${emit(consequent)} : ${emit(alternate)})`;
1134
1227
  }
@@ -58,8 +58,45 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
58
58
  * true — selecting the string operator from the operand's type avoids that.
59
59
  */
60
60
  private stringValueNames;
61
+ /**
62
+ * Module-scope pure string-literal constants (`const X = 'literal'` at
63
+ * file top-level), keyed by name → resolved literal value. Populated at
64
+ * `generate()` entry from `ir.metadata.localConstants`. When an identifier
65
+ * in an expression resolves to one of these, the adapter inlines the
66
+ * literal instead of emitting `$X` against a stash variable that is never
67
+ * bound (a module const isn't a prop, signal, or local — the value would
68
+ * render empty). Hono inlines it for free; this restores parity. Only
69
+ * module-scope pure string literals qualify (see `collectModuleStringConsts`).
70
+ */
71
+ private moduleStringConsts;
72
+ /**
73
+ * Names currently bound by an enclosing loop body — the `my $<param>` and
74
+ * `my $<index>` bindings `renderLoop` introduces — ref-counted so nested
75
+ * loops compose. `resolveModuleStringConst` consults this so a loop
76
+ * variable whose name happens to match a module string const is NOT
77
+ * inlined as the const literal (mirrors the Go adapter's loop-param /
78
+ * loop-var shadowing guards). (#1749 review)
79
+ */
80
+ private loopBoundNames;
61
81
  constructor(options?: MojoAdapterOptions);
62
82
  generate(ir: ComponentIR, options?: AdapterGenerateOptions): AdapterOutput;
83
+ /**
84
+ * Build the module pure-string-const map from the IR's localConstants.
85
+ * A const qualifies only when it is module-scope (`isModule`) and its
86
+ * initializer parses to a single string literal (`ts.StringLiteral` or
87
+ * `ts.NoSubstitutionTemplateLiteral`). Template literals with `${}`,
88
+ * numeric/object initializers, `Record<T,string>` maps, memos, and
89
+ * signals are all excluded — only a pure compile-time string can be
90
+ * inlined byte-for-byte.
91
+ */
92
+ private collectModuleStringConsts;
93
+ /**
94
+ * Resolve an identifier to its inlined Perl single-quoted string literal
95
+ * when it names a module pure-string const, else `null` (the caller then
96
+ * falls back to its normal `$name` stash lowering). Returns the Perl
97
+ * literal form `'<escaped>'` ready to drop into an expression.
98
+ */
99
+ resolveModuleStringConst(name: string): string | null;
63
100
  private generateScriptRegistrations;
64
101
  private hasClientInteractivity;
65
102
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/adapter/mojo-adapter.ts
2
+ import ts from "typescript";
2
3
  import {
3
4
  BaseAdapter,
4
5
  isBooleanAttr,
@@ -80,6 +81,22 @@ function resolveJsxChildrenProp(props) {
80
81
  return [];
81
82
  return prop.value.children;
82
83
  }
84
+ function parsePureStringLiteral(source) {
85
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
86
+ const stmt = sf.statements[0];
87
+ if (!stmt || !ts.isVariableStatement(stmt))
88
+ return null;
89
+ const decl = stmt.declarationList.declarations[0];
90
+ let init = decl?.initializer;
91
+ while (init && ts.isParenthesizedExpression(init))
92
+ init = init.expression;
93
+ if (!init)
94
+ return null;
95
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
96
+ return init.text;
97
+ }
98
+ return null;
99
+ }
83
100
 
84
101
  class MojoAdapter extends BaseAdapter {
85
102
  name = "mojolicious";
@@ -94,6 +111,8 @@ class MojoAdapter extends BaseAdapter {
94
111
  propsObjectName = null;
95
112
  propsParams = [];
96
113
  stringValueNames = new Set;
114
+ moduleStringConsts = new Map;
115
+ loopBoundNames = new Map;
97
116
  constructor(options = {}) {
98
117
  super();
99
118
  this.options = {
@@ -115,6 +134,8 @@ class MojoAdapter extends BaseAdapter {
115
134
  if (isStringTypeInfo(p.type))
116
135
  this.stringValueNames.add(p.name);
117
136
  }
137
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
138
+ this.loopBoundNames.clear();
118
139
  this.errors = [];
119
140
  this.childrenCaptureCounter = 0;
120
141
  if (!options?.siblingTemplatesRegistered) {
@@ -139,6 +160,27 @@ class MojoAdapter extends BaseAdapter {
139
160
  extension: this.extension
140
161
  };
141
162
  }
163
+ collectModuleStringConsts(constants) {
164
+ const map = new Map;
165
+ for (const c of constants ?? []) {
166
+ if (!c.isModule)
167
+ continue;
168
+ if (c.value === undefined)
169
+ continue;
170
+ const literal = parsePureStringLiteral(c.value);
171
+ if (literal !== null)
172
+ map.set(c.name, literal);
173
+ }
174
+ return map;
175
+ }
176
+ resolveModuleStringConst(name) {
177
+ if (this.loopBoundNames.has(name))
178
+ return null;
179
+ const value = this.moduleStringConsts.get(name);
180
+ if (value === undefined)
181
+ return null;
182
+ return `'${value.replace(/[\\']/g, (m) => `\\${m}`)}'`;
183
+ }
142
184
  generateScriptRegistrations(ir, scriptBaseName) {
143
185
  const hasInteractivity = this.hasClientInteractivity(ir);
144
186
  if (!hasInteractivity)
@@ -405,6 +447,10 @@ ${whenTrue}
405
447
  }
406
448
  const param = loop.param;
407
449
  const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
450
+ const loopBound = loop.iterationShape === "keys" ? [param] : [param, loop.index ?? "_i"];
451
+ for (const n of loopBound) {
452
+ this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
453
+ }
408
454
  const prevInLoop = this.inLoop;
409
455
  this.inLoop = true;
410
456
  const renderedChildren = this.renderChildren(loop.children);
@@ -438,6 +484,13 @@ ${renderedChildren}` : renderedChildren;
438
484
  } else {
439
485
  lines.push(children);
440
486
  }
487
+ for (const n of loopBound) {
488
+ const c = (this.loopBoundNames.get(n) ?? 1) - 1;
489
+ if (c <= 0)
490
+ this.loopBoundNames.delete(n);
491
+ else
492
+ this.loopBoundNames.set(n, c);
493
+ }
441
494
  lines.push(`% }`);
442
495
  lines.push(`<%== bf->comment("/loop:${loop.markerId}") %>`);
443
496
  return lines.join(`
@@ -884,6 +937,25 @@ function renderSortMethod(recv, c) {
884
937
  });
885
938
  return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
886
939
  }
940
+ function renderReduceMethod(recv, op, direction) {
941
+ const keyEntry = op.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${op.key.field}'`;
942
+ const init = op.type === "string" ? `'${op.init.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` : op.init;
943
+ return `bf->reduce(${recv}, { op => '${op.op}', ${keyEntry}, type => '${op.type}', init => ${init}, direction => '${direction}' })`;
944
+ }
945
+ function renderFlatMethod(recv, depth) {
946
+ const d = depth === "infinity" ? -1 : depth;
947
+ return `bf->flat(${recv}, ${d})`;
948
+ }
949
+ function renderFlatMapMethod(recv, op) {
950
+ const proj = op.projection;
951
+ if (proj.kind === "tuple") {
952
+ const specs = proj.elements.map((l) => l.kind === "self" ? `['self', '']` : `['field', '${l.field}']`).join(", ");
953
+ return `bf->flat_map_tuple(${recv}, ${specs})`;
954
+ }
955
+ if (proj.kind === "self")
956
+ return `bf->flat_map(${recv}, 'self', '')`;
957
+ return `bf->flat_map(${recv}, 'field', '${proj.field}')`;
958
+ }
887
959
  function isStringTypeInfo(type) {
888
960
  return type?.kind === "primitive" && type.primitive === "string";
889
961
  }
@@ -1008,6 +1080,15 @@ class MojoFilterEmitter {
1008
1080
  sortMethod(_method, object, comparator, emit) {
1009
1081
  return renderSortMethod(emit(object), comparator);
1010
1082
  }
1083
+ reduceMethod(method, object, reduceOp, emit) {
1084
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1085
+ }
1086
+ flatMethod(object, depth, emit) {
1087
+ return renderFlatMethod(emit(object), depth);
1088
+ }
1089
+ flatMapMethod(object, op, emit) {
1090
+ return renderFlatMapMethod(emit(object), op);
1091
+ }
1011
1092
  conditional(_test, _consequent, _alternate) {
1012
1093
  return "1";
1013
1094
  }
@@ -1028,6 +1109,9 @@ class MojoTopLevelEmitter {
1028
1109
  this.adapter = adapter;
1029
1110
  }
1030
1111
  identifier(name) {
1112
+ const inlined = this.adapter.resolveModuleStringConst(name);
1113
+ if (inlined !== null)
1114
+ return inlined;
1031
1115
  return `$${name}`;
1032
1116
  }
1033
1117
  literal(value, literalType) {
@@ -1129,6 +1213,15 @@ class MojoTopLevelEmitter {
1129
1213
  sortMethod(_method, object, comparator, emit) {
1130
1214
  return renderSortMethod(emit(object), comparator);
1131
1215
  }
1216
+ reduceMethod(method, object, reduceOp, emit) {
1217
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1218
+ }
1219
+ flatMethod(object, depth, emit) {
1220
+ return renderFlatMethod(emit(object), depth);
1221
+ }
1222
+ flatMapMethod(object, op, emit) {
1223
+ return renderFlatMapMethod(emit(object), op);
1224
+ }
1132
1225
  conditional(test, consequent, alternate, emit) {
1133
1226
  return `(${emit(test)} ? ${emit(consequent)} : ${emit(alternate)})`;
1134
1227
  }
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/adapter/mojo-adapter.ts
2
+ import ts from "typescript";
2
3
  import {
3
4
  BaseAdapter,
4
5
  isBooleanAttr,
@@ -80,6 +81,22 @@ function resolveJsxChildrenProp(props) {
80
81
  return [];
81
82
  return prop.value.children;
82
83
  }
84
+ function parsePureStringLiteral(source) {
85
+ const sf = ts.createSourceFile("__const.ts", `const __x = (${source});`, ts.ScriptTarget.Latest, false);
86
+ const stmt = sf.statements[0];
87
+ if (!stmt || !ts.isVariableStatement(stmt))
88
+ return null;
89
+ const decl = stmt.declarationList.declarations[0];
90
+ let init = decl?.initializer;
91
+ while (init && ts.isParenthesizedExpression(init))
92
+ init = init.expression;
93
+ if (!init)
94
+ return null;
95
+ if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
96
+ return init.text;
97
+ }
98
+ return null;
99
+ }
83
100
 
84
101
  class MojoAdapter extends BaseAdapter {
85
102
  name = "mojolicious";
@@ -94,6 +111,8 @@ class MojoAdapter extends BaseAdapter {
94
111
  propsObjectName = null;
95
112
  propsParams = [];
96
113
  stringValueNames = new Set;
114
+ moduleStringConsts = new Map;
115
+ loopBoundNames = new Map;
97
116
  constructor(options = {}) {
98
117
  super();
99
118
  this.options = {
@@ -115,6 +134,8 @@ class MojoAdapter extends BaseAdapter {
115
134
  if (isStringTypeInfo(p.type))
116
135
  this.stringValueNames.add(p.name);
117
136
  }
137
+ this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
138
+ this.loopBoundNames.clear();
118
139
  this.errors = [];
119
140
  this.childrenCaptureCounter = 0;
120
141
  if (!options?.siblingTemplatesRegistered) {
@@ -139,6 +160,27 @@ class MojoAdapter extends BaseAdapter {
139
160
  extension: this.extension
140
161
  };
141
162
  }
163
+ collectModuleStringConsts(constants) {
164
+ const map = new Map;
165
+ for (const c of constants ?? []) {
166
+ if (!c.isModule)
167
+ continue;
168
+ if (c.value === undefined)
169
+ continue;
170
+ const literal = parsePureStringLiteral(c.value);
171
+ if (literal !== null)
172
+ map.set(c.name, literal);
173
+ }
174
+ return map;
175
+ }
176
+ resolveModuleStringConst(name) {
177
+ if (this.loopBoundNames.has(name))
178
+ return null;
179
+ const value = this.moduleStringConsts.get(name);
180
+ if (value === undefined)
181
+ return null;
182
+ return `'${value.replace(/[\\']/g, (m) => `\\${m}`)}'`;
183
+ }
142
184
  generateScriptRegistrations(ir, scriptBaseName) {
143
185
  const hasInteractivity = this.hasClientInteractivity(ir);
144
186
  if (!hasInteractivity)
@@ -405,6 +447,10 @@ ${whenTrue}
405
447
  }
406
448
  const param = loop.param;
407
449
  const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
450
+ const loopBound = loop.iterationShape === "keys" ? [param] : [param, loop.index ?? "_i"];
451
+ for (const n of loopBound) {
452
+ this.loopBoundNames.set(n, (this.loopBoundNames.get(n) ?? 0) + 1);
453
+ }
408
454
  const prevInLoop = this.inLoop;
409
455
  this.inLoop = true;
410
456
  const renderedChildren = this.renderChildren(loop.children);
@@ -438,6 +484,13 @@ ${renderedChildren}` : renderedChildren;
438
484
  } else {
439
485
  lines.push(children);
440
486
  }
487
+ for (const n of loopBound) {
488
+ const c = (this.loopBoundNames.get(n) ?? 1) - 1;
489
+ if (c <= 0)
490
+ this.loopBoundNames.delete(n);
491
+ else
492
+ this.loopBoundNames.set(n, c);
493
+ }
441
494
  lines.push(`% }`);
442
495
  lines.push(`<%== bf->comment("/loop:${loop.markerId}") %>`);
443
496
  return lines.join(`
@@ -884,6 +937,25 @@ function renderSortMethod(recv, c) {
884
937
  });
885
938
  return `bf->sort(${recv}, { keys => [${keyHashes.join(", ")}] })`;
886
939
  }
940
+ function renderReduceMethod(recv, op, direction) {
941
+ const keyEntry = op.key.kind === "self" ? `key_kind => 'self'` : `key_kind => 'field', key => '${op.key.field}'`;
942
+ const init = op.type === "string" ? `'${op.init.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` : op.init;
943
+ return `bf->reduce(${recv}, { op => '${op.op}', ${keyEntry}, type => '${op.type}', init => ${init}, direction => '${direction}' })`;
944
+ }
945
+ function renderFlatMethod(recv, depth) {
946
+ const d = depth === "infinity" ? -1 : depth;
947
+ return `bf->flat(${recv}, ${d})`;
948
+ }
949
+ function renderFlatMapMethod(recv, op) {
950
+ const proj = op.projection;
951
+ if (proj.kind === "tuple") {
952
+ const specs = proj.elements.map((l) => l.kind === "self" ? `['self', '']` : `['field', '${l.field}']`).join(", ");
953
+ return `bf->flat_map_tuple(${recv}, ${specs})`;
954
+ }
955
+ if (proj.kind === "self")
956
+ return `bf->flat_map(${recv}, 'self', '')`;
957
+ return `bf->flat_map(${recv}, 'field', '${proj.field}')`;
958
+ }
887
959
  function isStringTypeInfo(type) {
888
960
  return type?.kind === "primitive" && type.primitive === "string";
889
961
  }
@@ -1008,6 +1080,15 @@ class MojoFilterEmitter {
1008
1080
  sortMethod(_method, object, comparator, emit) {
1009
1081
  return renderSortMethod(emit(object), comparator);
1010
1082
  }
1083
+ reduceMethod(method, object, reduceOp, emit) {
1084
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1085
+ }
1086
+ flatMethod(object, depth, emit) {
1087
+ return renderFlatMethod(emit(object), depth);
1088
+ }
1089
+ flatMapMethod(object, op, emit) {
1090
+ return renderFlatMapMethod(emit(object), op);
1091
+ }
1011
1092
  conditional(_test, _consequent, _alternate) {
1012
1093
  return "1";
1013
1094
  }
@@ -1028,6 +1109,9 @@ class MojoTopLevelEmitter {
1028
1109
  this.adapter = adapter;
1029
1110
  }
1030
1111
  identifier(name) {
1112
+ const inlined = this.adapter.resolveModuleStringConst(name);
1113
+ if (inlined !== null)
1114
+ return inlined;
1031
1115
  return `$${name}`;
1032
1116
  }
1033
1117
  literal(value, literalType) {
@@ -1129,6 +1213,15 @@ class MojoTopLevelEmitter {
1129
1213
  sortMethod(_method, object, comparator, emit) {
1130
1214
  return renderSortMethod(emit(object), comparator);
1131
1215
  }
1216
+ reduceMethod(method, object, reduceOp, emit) {
1217
+ return renderReduceMethod(emit(object), reduceOp, method === "reduceRight" ? "right" : "left");
1218
+ }
1219
+ flatMethod(object, depth, emit) {
1220
+ return renderFlatMethod(emit(object), depth);
1221
+ }
1222
+ flatMapMethod(object, op, emit) {
1223
+ return renderFlatMapMethod(emit(object), op);
1224
+ }
1132
1225
  conditional(test, consequent, alternate, emit) {
1133
1226
  return `(${emit(test)} ? ${emit(consequent)} : ${emit(alternate)})`;
1134
1227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxE;AAkCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,OAAO,iBAAiB,EAAE,eAAe,CAAA;IAClD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MjF;AAgND;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAuBT"}
1
+ {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxE;AAkCD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,OAAO,iBAAiB,EAAE,eAAe,CAAA;IAClD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MjF;AAsOD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAuBT"}
@@ -0,0 +1,91 @@
1
+ package BarefootJS::Backend::Mojo;
2
+ our $VERSION = "0.01";
3
+ use Mojo::Base -base, -signatures;
4
+
5
+ use Mojo::ByteStream qw(b);
6
+ use Mojo::JSON qw(to_json);
7
+ use Scalar::Util qw(weaken);
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Reference rendering backend (Mojolicious / Mojo::Template).
11
+ # ---------------------------------------------------------------------------
12
+ #
13
+ # BarefootJS.pm holds all the template-engine-agnostic logic (the JS-compat
14
+ # value helpers, array/string methods, hydration markers). Everything that is
15
+ # specific to *how a template is rendered* — JSON marshalling, raw-string
16
+ # marking, JSX-children materialisation, and named-template rendering — lives
17
+ # behind this backend object so the same runtime can drive a different Perl
18
+ # template engine (Text::Xslate, Template Toolkit, …) without rewriting the
19
+ # helper surface.
20
+ #
21
+ # A backend MUST implement:
22
+ # - encode_json($data) -> string
23
+ # - mark_raw($str) -> value the engine emits without escaping
24
+ # - materialize($value) -> string (resolve a captured-children ref)
25
+ # - render_named($name, $bf, \%vars) -> string
26
+ #
27
+ # This Mojo implementation is the reference. To target another engine, write a
28
+ # sibling backend (BarefootJS::Backend::Xslate, …) implementing the same four
29
+ # methods and pass it via `BarefootJS->new($c, { backend => $b })`.
30
+
31
+ # The Mojolicious controller. Optional: the value-marshalling helpers
32
+ # (`encode_json` / `mark_raw` / `materialize`) work without it; only
33
+ # `render_named` reaches into the controller's renderer + stash.
34
+ has 'c';
35
+
36
+ # Pluggable JSON encoder (#engine-abstraction). Defaults to
37
+ # `Mojo::JSON::to_json`, which returns a *character* string (not bytes)
38
+ # suitable for embedding in HTML output via `<%==` / Mojo::ByteStream.
39
+ #
40
+ # Override with any `sub ($data) { ... }` to swap in a faster XS encoder —
41
+ # e.g. `json_encoder => sub { Cpanel::JSON::XS->new->canonical->encode($_[0]) }`.
42
+ # The pure-Perl JSON::PP fallback Mojo::JSON uses can be a hot spot for large
43
+ # props payloads; the seam lets a host pick its own implementation without
44
+ # touching the runtime.
45
+ has 'json_encoder' => sub { \&to_json };
46
+
47
+ # Hold the controller weakly for the same reason BarefootJS does: the
48
+ # controller owns the bf instance (which owns this backend) via its stash,
49
+ # so a strong back-reference would close a per-request cycle the refcount GC
50
+ # can't reclaim. `render_named` only touches `$self->c` mid-render, while the
51
+ # controller is still alive on the request stack.
52
+ sub new ($class, %args) {
53
+ my $self = $class->SUPER::new(%args);
54
+ weaken($self->{c}) if $self->{c};
55
+ return $self;
56
+ }
57
+
58
+ sub encode_json ($self, $data) {
59
+ return $self->json_encoder->($data);
60
+ }
61
+
62
+ # Mark a string as already-safe so the template engine emits it verbatim
63
+ # (no re-escaping). In Mojo this is a Mojo::ByteStream, which the calling
64
+ # template's `<%==` raw-emit passes through unescaped.
65
+ sub mark_raw ($self, $str) {
66
+ return b($str);
67
+ }
68
+
69
+ # JSX children / async fallbacks arrive via Mojo's `begin %>...<% end`
70
+ # capture, which produces a CODE ref returning a Mojo::ByteStream. Resolve
71
+ # it to a string before embedding. Plain (already-rendered) strings pass
72
+ # through unchanged.
73
+ sub materialize ($self, $value) {
74
+ return ref($value) eq 'CODE' ? $value->() : $value;
75
+ }
76
+
77
+ # Render a named template with `$child_bf` bound as the active runtime
78
+ # instance for that render. The Mojo `bf` helper resolves the current
79
+ # instance off `$c->stash->{'bf.instance'}`; swap it for the duration of
80
+ # the nested render and restore it afterwards so sibling renders are
81
+ # unaffected.
82
+ sub render_named ($self, $template_name, $child_bf, $vars) {
83
+ my $c = $self->c;
84
+ my $prev = $c->stash->{'bf.instance'};
85
+ $c->stash->{'bf.instance'} = $child_bf;
86
+ my $html = $c->render_to_string(template => $template_name, %$vars);
87
+ $c->stash->{'bf.instance'} = $prev;
88
+ return $html;
89
+ }
90
+
91
+ 1;
@@ -1,4 +1,5 @@
1
1
  package Mojolicious::Plugin::BarefootJS::DevReload;
2
+ our $VERSION = "0.01";
2
3
  use Mojo::Base 'Mojolicious::Plugin', -signatures;
3
4
 
4
5
  =head1 NAME