@transclude/core 0.1.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/LICENSE +21 -0
- package/README.md +121 -0
- package/bin/build.js +469 -0
- package/bin/check.js +78 -0
- package/bin/dev.js +348 -0
- package/bin/release.js +176 -0
- package/bin/serve.bun.js +15 -0
- package/bin/serve.deno.js +15 -0
- package/bin/serve.js +12 -0
- package/editor/server.js +172 -0
- package/editor/vscode/extension.js +49 -0
- package/editor/vscode/package.json +32 -0
- package/editor/vscode/syntaxes/transclude.injection.json +41 -0
- package/package.json +82 -0
- package/src/address.js +183 -0
- package/src/app.js +492 -0
- package/src/cache.js +137 -0
- package/src/compiler/bind.js +496 -0
- package/src/compiler/codegen.js +1061 -0
- package/src/compiler/expr.js +221 -0
- package/src/compiler/index.js +964 -0
- package/src/compiler/interp.js +82 -0
- package/src/compiler/script.js +620 -0
- package/src/compiler/shim.js +756 -0
- package/src/compiler/sourcemap.js +140 -0
- package/src/compiler/types.js +163 -0
- package/src/compress.js +104 -0
- package/src/cookies.js +157 -0
- package/src/csp.js +192 -0
- package/src/document.js +604 -0
- package/src/extract.js +339 -0
- package/src/feed.js +194 -0
- package/src/include.js +89 -0
- package/src/lookup.js +49 -0
- package/src/negotiate.js +95 -0
- package/src/plugin.js +423 -0
- package/src/pool.js +29 -0
- package/src/precache.js +68 -0
- package/src/production.js +159 -0
- package/src/project.js +110 -0
- package/src/proxy.js +319 -0
- package/src/public-files.js +77 -0
- package/src/rewrite.js +281 -0
- package/src/routes.js +199 -0
- package/src/runtime/index.js +1345 -0
- package/src/server.js +183 -0
- package/src/sitemap.js +124 -0
- package/src/static-cache.js +170 -0
- package/src/typecheck.js +492 -0
- package/src/worker.js +87 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Expression layer.
|
|
2
|
+
//
|
|
3
|
+
// jsep gives a small grammar: no assignment, no arrow functions, no object
|
|
4
|
+
// literals, no `new`. That keeps a template declarative rather than a second place
|
|
5
|
+
// to write code, and it is what makes collecting references below possible. The
|
|
6
|
+
// same thing lets `${user.nmae}` be checked against a known shape.
|
|
7
|
+
|
|
8
|
+
import jsep from 'jsep';
|
|
9
|
+
|
|
10
|
+
if (!jsep.binary_ops['??']) jsep.addBinaryOp('??', 1);
|
|
11
|
+
|
|
12
|
+
// Identifiers resolvable without being template data.
|
|
13
|
+
export const GLOBALS = new Set([
|
|
14
|
+
'html',
|
|
15
|
+
// The one interpolation a <script> may carry. It has to resolve to the runtime
|
|
16
|
+
// function rather than to a field of the page's data, or the guard that lets it
|
|
17
|
+
// through would be naming something the author never has.
|
|
18
|
+
'json',
|
|
19
|
+
'Math',
|
|
20
|
+
'JSON',
|
|
21
|
+
'String',
|
|
22
|
+
'Number',
|
|
23
|
+
'Boolean',
|
|
24
|
+
'Array',
|
|
25
|
+
'Object',
|
|
26
|
+
'Date',
|
|
27
|
+
'isNaN',
|
|
28
|
+
'parseInt',
|
|
29
|
+
'parseFloat',
|
|
30
|
+
'undefined',
|
|
31
|
+
'NaN',
|
|
32
|
+
'Infinity',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The names in scope at a point in the template, innermost first.
|
|
37
|
+
*
|
|
38
|
+
* A name a scope holds is a loop variable or a block binding and compiles to
|
|
39
|
+
* itself. Anything else is a field of the page's data and compiles to a lookup,
|
|
40
|
+
* which is what makes `${title}` mean `__d.title` with nothing declared.
|
|
41
|
+
*/
|
|
42
|
+
export class Scope {
|
|
43
|
+
constructor(parent = null) {
|
|
44
|
+
this.parent = parent;
|
|
45
|
+
this.vars = new Map();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare(name, js, shape) {
|
|
49
|
+
this.vars.set(name, { js, shape });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
lookup(name) {
|
|
53
|
+
for (let s = this; s; s = s.parent) {
|
|
54
|
+
if (s.vars.has(name)) return s.vars.get(name);
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Used for the shadowing warning: does an *enclosing* scope already bind this?
|
|
60
|
+
outerHas(name) {
|
|
61
|
+
for (let s = this.parent; s; s = s.parent) {
|
|
62
|
+
if (s.vars.has(name)) return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A template expression as an AST.
|
|
70
|
+
*
|
|
71
|
+
* Directive values are expressions, not interpolations: `each="tag of tags"` has
|
|
72
|
+
* no `${}` around it, so it is parsed rather than split.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} source
|
|
75
|
+
* @returns {object} a jsep node
|
|
76
|
+
* @throws on an empty or unparseable expression
|
|
77
|
+
*/
|
|
78
|
+
export function parseExpr(source) {
|
|
79
|
+
const src = String(source).trim();
|
|
80
|
+
if (!src) throw new Error('empty expression');
|
|
81
|
+
return jsep(src);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* An AST back to JavaScript, with every free name resolved against `scope`.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} node a jsep node
|
|
88
|
+
* @param {Scope} scope
|
|
89
|
+
* @returns {string} an expression, safe to place inside the generated render
|
|
90
|
+
*/
|
|
91
|
+
export function emit(node, scope) {
|
|
92
|
+
switch (node.type) {
|
|
93
|
+
case 'Literal':
|
|
94
|
+
return node.raw !== undefined ? node.raw : JSON.stringify(node.value);
|
|
95
|
+
|
|
96
|
+
case 'Identifier': {
|
|
97
|
+
const local = scope.lookup(node.name);
|
|
98
|
+
if (local) return local.js;
|
|
99
|
+
if (GLOBALS.has(node.name)) return node.name;
|
|
100
|
+
return `__d[${JSON.stringify(node.name)}]`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case 'MemberExpression':
|
|
104
|
+
return node.computed
|
|
105
|
+
? `${emit(node.object, scope)}[${emit(node.property, scope)}]`
|
|
106
|
+
: `${emit(node.object, scope)}.${node.property.name}`;
|
|
107
|
+
|
|
108
|
+
case 'CallExpression':
|
|
109
|
+
return `${emit(node.callee, scope)}(${node.arguments.map((a) => emit(a, scope)).join(', ')})`;
|
|
110
|
+
|
|
111
|
+
case 'UnaryExpression':
|
|
112
|
+
return `(${node.operator}${emit(node.argument, scope)})`;
|
|
113
|
+
|
|
114
|
+
case 'BinaryExpression':
|
|
115
|
+
case 'LogicalExpression':
|
|
116
|
+
return `(${emit(node.left, scope)} ${node.operator} ${emit(node.right, scope)})`;
|
|
117
|
+
|
|
118
|
+
case 'ConditionalExpression':
|
|
119
|
+
return `(${emit(node.test, scope)} ? ${emit(node.consequent, scope)} : ${emit(node.alternate, scope)})`;
|
|
120
|
+
|
|
121
|
+
case 'ArrayExpression':
|
|
122
|
+
return `[${node.elements.map((e) => emit(e, scope)).join(', ')}]`;
|
|
123
|
+
|
|
124
|
+
case 'Compound':
|
|
125
|
+
throw new Error('expected a single expression, found several (stray `;` or `,`?)');
|
|
126
|
+
|
|
127
|
+
case 'ThisExpression':
|
|
128
|
+
throw new Error('`this` is not available in templates');
|
|
129
|
+
|
|
130
|
+
default:
|
|
131
|
+
throw new Error(`unsupported expression node: ${node.type}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @typedef {object} Chain
|
|
137
|
+
* @property {'data'|'scope'} base what the path is rooted at
|
|
138
|
+
* @property {string} name the root's name
|
|
139
|
+
* @property {string[]} path the static segments read from it
|
|
140
|
+
* @property {boolean} open false once a computed access ended the path
|
|
141
|
+
* @property {object} [shape] the loop variable's element type, when known
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The longest static property path rooted at template data or a loop variable,
|
|
146
|
+
* or null when the root is something we cannot follow (a call result, a
|
|
147
|
+
* literal). A computed access ends the path, so `a.b[i].c` gives `a.b`. Past `[i]`
|
|
148
|
+
* there is no way to know what is being read.
|
|
149
|
+
*
|
|
150
|
+
* @param {object} node
|
|
151
|
+
* @param {Scope} scope
|
|
152
|
+
* @param {object[]} [computed] collects the subscript expressions, which are
|
|
153
|
+
* themselves reads and have to be walked separately
|
|
154
|
+
* @returns {Chain|null}
|
|
155
|
+
*/
|
|
156
|
+
export function chainOf(node, scope, computed = []) {
|
|
157
|
+
if (node.type === 'Identifier') {
|
|
158
|
+
const local = scope.lookup(node.name);
|
|
159
|
+
if (local) return { base: 'scope', name: node.name, shape: local.shape, path: [], open: true };
|
|
160
|
+
if (GLOBALS.has(node.name)) return null;
|
|
161
|
+
return { base: 'data', name: node.name, path: [], open: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (node.type === 'MemberExpression') {
|
|
165
|
+
const inner = chainOf(node.object, scope, computed);
|
|
166
|
+
if (!inner) return null;
|
|
167
|
+
if (node.computed) {
|
|
168
|
+
computed.push(node.property);
|
|
169
|
+
return { ...inner, open: false };
|
|
170
|
+
}
|
|
171
|
+
if (inner.open) return { ...inner, path: [...inner.path, node.property.name] };
|
|
172
|
+
return inner;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Every data or loop-variable path an expression reads.
|
|
180
|
+
*
|
|
181
|
+
* This is what decides which bindings are volatile, and so whether a light
|
|
182
|
+
* element can write an update in place or needs a shadow root to rebuild.
|
|
183
|
+
*
|
|
184
|
+
* @param {object} node
|
|
185
|
+
* @param {Scope} scope
|
|
186
|
+
* @param {Chain[]} [out] accumulator, so a caller can collect across several
|
|
187
|
+
* @returns {Chain[]} one entry per read, in the order they were found
|
|
188
|
+
*/
|
|
189
|
+
export function collectRefs(node, scope, out = []) {
|
|
190
|
+
if (!node || typeof node !== 'object') return out;
|
|
191
|
+
|
|
192
|
+
switch (node.type) {
|
|
193
|
+
case 'Identifier':
|
|
194
|
+
case 'MemberExpression': {
|
|
195
|
+
const computed = [];
|
|
196
|
+
const chain = chainOf(node, scope, computed);
|
|
197
|
+
if (chain) {
|
|
198
|
+
out.push(chain);
|
|
199
|
+
for (const inner of computed) collectRefs(inner, scope, out);
|
|
200
|
+
} else if (node.type === 'MemberExpression') {
|
|
201
|
+
collectRefs(node.object, scope, out);
|
|
202
|
+
if (node.computed) collectRefs(node.property, scope, out);
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'CallExpression':
|
|
208
|
+
// The callee path is collected in full: a method that does not exist is
|
|
209
|
+
// as much a typo as a property that does not.
|
|
210
|
+
collectRefs(node.callee, scope, out);
|
|
211
|
+
for (const arg of node.arguments) collectRefs(arg, scope, out);
|
|
212
|
+
return out;
|
|
213
|
+
|
|
214
|
+
default:
|
|
215
|
+
for (const key of ['argument', 'left', 'right', 'test', 'consequent', 'alternate', 'object', 'property']) {
|
|
216
|
+
if (node[key]) collectRefs(node[key], scope, out);
|
|
217
|
+
}
|
|
218
|
+
for (const element of node.elements ?? []) collectRefs(element, scope, out);
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
}
|