@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.
- package/dist/adapter/index.js +93 -0
- package/dist/adapter/mojo-adapter.d.ts +37 -0
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/build.js +93 -0
- package/dist/index.js +93 -0
- package/dist/test-render.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Mojo.pm +91 -0
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -0
- package/lib/Mojolicious/Plugin/BarefootJS.pm +56 -0
- package/package.json +7 -6
- package/src/__tests__/mojo-adapter.test.ts +148 -16
- package/src/adapter/mojo-adapter.ts +191 -2
- package/src/test-render.ts +30 -3
- package/lib/BarefootJS.pm +0 -938
package/dist/adapter/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
|
}
|
|
@@ -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":"AAAA;;;;GAIG;
|
|
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;
|
|
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;
|