wc-compiler 0.20.0 → 0.21.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.
- package/package.json +8 -5
- package/src/dom-shim.js +4 -0
- package/src/effect.js +44 -0
- package/src/jsx-loader.js +519 -179
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wc-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "WCC supports server-rendering of standard Web Components, generating HTML directly from your custom element definitions.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"./register": "./src/register.js",
|
|
18
18
|
"./dom-shim": "./src/dom-shim.js",
|
|
19
19
|
"./jsx-loader": "./src/jsx-loader.js",
|
|
20
|
-
"./jsx-runtime": "./src/jsx-runtime.ts"
|
|
20
|
+
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
21
|
+
"./effect": "./src/effect.js"
|
|
21
22
|
},
|
|
22
23
|
"author": "Owen Buckley <owen@thegreenhouse.io>",
|
|
23
24
|
"keywords": [
|
|
@@ -37,9 +38,9 @@
|
|
|
37
38
|
"access": "public"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
|
-
"dev": "greenwood develop",
|
|
41
|
-
"build": "NODE_OPTIONS='--import @greenwood/cli/register' greenwood build",
|
|
42
|
-
"serve": "greenwood serve",
|
|
41
|
+
"docs:dev": "greenwood develop",
|
|
42
|
+
"docs:build": "NODE_OPTIONS='--import @greenwood/cli/register' greenwood build",
|
|
43
|
+
"docs:serve": "greenwood serve",
|
|
43
44
|
"lint": "npm run lint:js && npm run lint:ls && npm run lint:css",
|
|
44
45
|
"lint:js": "eslint",
|
|
45
46
|
"lint:ls": "ls-lint",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"acorn-walk": "^8.3.4",
|
|
62
63
|
"astring": "^1.9.0",
|
|
63
64
|
"parse5": "^7.2.1",
|
|
65
|
+
"signal-polyfill": "^0.2.2",
|
|
64
66
|
"sucrase": "^3.35.0"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
@@ -89,6 +91,7 @@
|
|
|
89
91
|
"lint-staged": "^16.2.6",
|
|
90
92
|
"mocha": "^9.2.2",
|
|
91
93
|
"open-props": "^1.7.4",
|
|
94
|
+
"patch-package": "^8.0.1",
|
|
92
95
|
"playwright": "^1.58.2",
|
|
93
96
|
"prettier": "^3.6.2",
|
|
94
97
|
"prism-themes": "^1.9.0",
|
package/src/dom-shim.js
CHANGED
|
@@ -149,6 +149,10 @@ class Node extends EventTarget {
|
|
|
149
149
|
this.childNodes.push(textNode);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
|
|
153
|
+
// minimal shim to support JSX <> Signals compilation and caching DOM references tracked to effects
|
|
154
|
+
querySelector() {}
|
|
155
|
+
querySelectorAll() {}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
// https://developer.mozilla.org/en-US/docs/Web/API/Element
|
package/src/effect.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// https://github.com/proposal-signals/signal-polyfill?tab=readme-ov-file#creating-a-simple-effect
|
|
2
|
+
import { Signal } from 'signal-polyfill';
|
|
3
|
+
|
|
4
|
+
let needsEnqueue = true;
|
|
5
|
+
|
|
6
|
+
const w = new Signal.subtle.Watcher(() => {
|
|
7
|
+
if (needsEnqueue) {
|
|
8
|
+
needsEnqueue = false;
|
|
9
|
+
queueMicrotask(processPending);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function processPending() {
|
|
14
|
+
needsEnqueue = true;
|
|
15
|
+
|
|
16
|
+
for (const s of w.getPending()) {
|
|
17
|
+
s.get();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
w.watch();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function effect(callback) {
|
|
24
|
+
// export effect function as a no-op for SSR environments
|
|
25
|
+
if (typeof window === 'undefined') {
|
|
26
|
+
return () => {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let cleanup;
|
|
30
|
+
|
|
31
|
+
const computed = new Signal.Computed(() => {
|
|
32
|
+
typeof cleanup === 'function' && cleanup();
|
|
33
|
+
cleanup = callback();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
w.watch(computed);
|
|
37
|
+
computed.get();
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
w.unwatch(computed);
|
|
41
|
+
typeof cleanup === 'function' && cleanup();
|
|
42
|
+
cleanup = undefined;
|
|
43
|
+
};
|
|
44
|
+
}
|
package/src/jsx-loader.js
CHANGED
|
@@ -6,19 +6,27 @@ import fs from 'fs';
|
|
|
6
6
|
// ideally we can eventually adopt an ESM compatible version of this plugin
|
|
7
7
|
// https://github.com/acornjs/acorn-jsx/issues/112
|
|
8
8
|
// @ts-ignore
|
|
9
|
-
// but it does have a default export
|
|
9
|
+
// but it does have a default export?
|
|
10
10
|
import jsx from '@projectevergreen/acorn-jsx-esm';
|
|
11
11
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
12
12
|
import { transform } from 'sucrase';
|
|
13
13
|
|
|
14
|
+
// expose global Signal polyfill to SSR environment
|
|
15
|
+
import { Signal } from 'signal-polyfill';
|
|
16
|
+
globalThis.Signal = Signal;
|
|
17
|
+
|
|
14
18
|
const jsxRegex = /\.(jsx)$/;
|
|
15
19
|
const tsxRegex = /\.(tsx)$/;
|
|
20
|
+
const acornParseOptions = {
|
|
21
|
+
ecmaVersion: 'latest',
|
|
22
|
+
sourceType: 'module',
|
|
23
|
+
};
|
|
16
24
|
|
|
17
|
-
// TODO same hack as definitions
|
|
25
|
+
// TODO: same hack as definitions
|
|
18
26
|
// https://github.com/ProjectEvergreen/wcc/discussions/74
|
|
19
27
|
let string;
|
|
20
28
|
|
|
21
|
-
// TODO move to a util
|
|
29
|
+
// TODO: move to a util
|
|
22
30
|
// https://github.com/ProjectEvergreen/wcc/discussions/74
|
|
23
31
|
function getParse(html) {
|
|
24
32
|
return html.indexOf('<html>') >= 0 || html.indexOf('<body>') >= 0 || html.indexOf('<head>') >= 0
|
|
@@ -66,6 +74,9 @@ function applyDomDepthSubstitutions(tree, currentDepth = 1, hasShadowRoot = fals
|
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
// TODO: handle text nodes for __this__ references
|
|
78
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
79
|
+
|
|
69
80
|
if (node.childNodes && node.childNodes.length > 0) {
|
|
70
81
|
applyDomDepthSubstitutions(node, currentDepth + 1, hasShadowRoot);
|
|
71
82
|
}
|
|
@@ -77,7 +88,47 @@ function applyDomDepthSubstitutions(tree, currentDepth = 1, hasShadowRoot = fals
|
|
|
77
88
|
return tree;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
// TODO handle if / else statements
|
|
92
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
93
|
+
function findThisReferences(context, statement) {
|
|
94
|
+
const references = [];
|
|
95
|
+
const isRenderFunctionContext = context === 'render';
|
|
96
|
+
const { expression, type } = statement;
|
|
97
|
+
const isConstructorThisAssignment =
|
|
98
|
+
context === 'constructor' &&
|
|
99
|
+
type === 'ExpressionStatement' &&
|
|
100
|
+
expression.type === 'AssignmentExpression' &&
|
|
101
|
+
expression.left.object.type === 'ThisExpression';
|
|
102
|
+
|
|
103
|
+
if (isConstructorThisAssignment) {
|
|
104
|
+
// this.name = 'something'; // constructor
|
|
105
|
+
references.push(expression.left.property.name);
|
|
106
|
+
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
107
|
+
statement.declarations.forEach((declaration) => {
|
|
108
|
+
const { init, id } = declaration;
|
|
109
|
+
|
|
110
|
+
if (init.object && init.object.type === 'ThisExpression') {
|
|
111
|
+
// const { description } = this.todo;
|
|
112
|
+
references.push(init.property.name);
|
|
113
|
+
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
114
|
+
// const { id, description } = this;
|
|
115
|
+
id.properties.forEach((property) => {
|
|
116
|
+
references.push(property.key.name);
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
// TODO: we are just blindly tracking anything here?
|
|
120
|
+
// everything should ideally be mapped to actual this references, to create a strong chain of direct reactivity
|
|
121
|
+
// instead of tracking any declaration as a derived tracking attr
|
|
122
|
+
// for convenience here, we push the entire declaration here, instead of the name like for direct this references (see above)
|
|
123
|
+
references.push(declaration);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return references;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseJsxElement(element, moduleContents = '', inferredObservability) {
|
|
81
132
|
try {
|
|
82
133
|
const { type } = element;
|
|
83
134
|
|
|
@@ -124,25 +175,37 @@ function parseJsxElement(element, moduleContents = '', inferredObservability = f
|
|
|
124
175
|
|
|
125
176
|
if (left.object.type === 'ThisExpression') {
|
|
126
177
|
if (left.property.type === 'Identifier') {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// string += ` ${name}="__this__.${left.property.name}${expression.operator}${right.raw}; __this__.update(\\'${left.property.name}\\', null, __this__.${left.property.name});"`;
|
|
130
|
-
string += ` ${name}="__this__.${left.property.name}${expression.operator}${right.raw}; __this__.setAttribute(\\'${left.property.name}\\', __this__.${left.property.name});"`;
|
|
131
|
-
} else {
|
|
132
|
-
// implicit reactivity using this.render
|
|
133
|
-
string += ` ${name}="__this__.${left.property.name}${expression.operator}${right.raw}; __this__.render();"`;
|
|
134
|
-
}
|
|
178
|
+
// implicit reactivity using this.render
|
|
179
|
+
string += ` ${name}="__this__.${left.property.name}${expression.operator}${right.raw}; __this__.render();"`;
|
|
135
180
|
}
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
183
|
}
|
|
139
184
|
} else if (attribute.name.type === 'JSXIdentifier') {
|
|
140
|
-
// TODO is there any difference between an attribute for an event handler vs a normal attribute?
|
|
141
|
-
// Can all these be parsed using one function>
|
|
142
185
|
if (attribute.value) {
|
|
186
|
+
const expression = attribute?.value?.expression;
|
|
143
187
|
if (attribute.value.type === 'Literal') {
|
|
144
188
|
// xxx="yyy" >
|
|
145
189
|
string += ` ${name}="${attribute.value.value}"`;
|
|
190
|
+
} else if (
|
|
191
|
+
expression &&
|
|
192
|
+
inferredObservability &&
|
|
193
|
+
attribute.value.type === 'JSXExpressionContainer' &&
|
|
194
|
+
expression?.type === 'CallExpression' &&
|
|
195
|
+
expression?.callee.type === 'MemberExpression' &&
|
|
196
|
+
expression?.arguments &&
|
|
197
|
+
expression?.callee?.property?.name === 'get'
|
|
198
|
+
) {
|
|
199
|
+
// xxx={products.get().length} >
|
|
200
|
+
const { object, property } = expression.callee;
|
|
201
|
+
|
|
202
|
+
if (object.type === 'MemberExpression' && object?.object.type === 'ThisExpression') {
|
|
203
|
+
// The count is counter={this.count.get()}
|
|
204
|
+
string += ` ${name}=$\{${object.property.name}.${property.name}()}`;
|
|
205
|
+
} else if (object.type === 'Identifier') {
|
|
206
|
+
// xxx={products.get().length} >
|
|
207
|
+
string += ` ${name}=$\{${object.name}.${property.name}()}`;
|
|
208
|
+
}
|
|
146
209
|
} else if (attribute.value.type === 'JSXExpressionContainer') {
|
|
147
210
|
// xxx={allTodos.length} >
|
|
148
211
|
const { value } = attribute;
|
|
@@ -166,11 +229,6 @@ function parseJsxElement(element, moduleContents = '', inferredObservability = f
|
|
|
166
229
|
default:
|
|
167
230
|
break;
|
|
168
231
|
}
|
|
169
|
-
|
|
170
|
-
// only apply this when dealing with `this` references
|
|
171
|
-
if (inferredObservability) {
|
|
172
|
-
string += ` data-wcc-${expression.name}="${name}" data-wcc-ins="attr"`;
|
|
173
|
-
}
|
|
174
232
|
}
|
|
175
233
|
} else {
|
|
176
234
|
// xxx >
|
|
@@ -197,15 +255,19 @@ function parseJsxElement(element, moduleContents = '', inferredObservability = f
|
|
|
197
255
|
if (type === 'JSXExpressionContainer') {
|
|
198
256
|
const { type } = element.expression;
|
|
199
257
|
|
|
200
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
inferredObservability &&
|
|
260
|
+
type === 'CallExpression' &&
|
|
261
|
+
element.expression.arguments &&
|
|
262
|
+
element.expression?.callee?.type === 'MemberExpression' &&
|
|
263
|
+
element.expression?.callee?.property?.name === 'get'
|
|
264
|
+
) {
|
|
265
|
+
// TODO: handle this references
|
|
266
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
267
|
+
const { object, property } = element.expression.callee;
|
|
268
|
+
string += `$\{${object.name}.${property.name}()}`;
|
|
269
|
+
} else if (type === 'Identifier') {
|
|
201
270
|
// You have {count} TODOs left to complete
|
|
202
|
-
if (inferredObservability) {
|
|
203
|
-
const { name } = element.expression;
|
|
204
|
-
|
|
205
|
-
string = `${string.slice(0, string.lastIndexOf('>'))} data-wcc-${name}="\${this.${name}}" data-wcc-ins="text">`;
|
|
206
|
-
}
|
|
207
|
-
// TODO be able to remove this extra data attribute
|
|
208
|
-
// string = `${string.slice(0, string.lastIndexOf('>'))} data-wcc-${name} data-wcc-ins="text">`;
|
|
209
271
|
string += `$\{${element.expression.name}}`;
|
|
210
272
|
} else if (type === 'MemberExpression') {
|
|
211
273
|
const { object } = element.expression.object;
|
|
@@ -230,44 +292,67 @@ function parseJsxElement(element, moduleContents = '', inferredObservability = f
|
|
|
230
292
|
return string;
|
|
231
293
|
}
|
|
232
294
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const references = [];
|
|
237
|
-
const isRenderFunctionContext = context === 'render';
|
|
238
|
-
const { expression, type } = statement;
|
|
239
|
-
const isConstructorThisAssignment =
|
|
240
|
-
context === 'constructor' &&
|
|
241
|
-
type === 'ExpressionStatement' &&
|
|
242
|
-
expression.type === 'AssignmentExpression' &&
|
|
243
|
-
expression.left.object.type === 'ThisExpression';
|
|
295
|
+
function generateEffectsForReactiveElements(reactiveElements) {
|
|
296
|
+
let effectsCount = 0;
|
|
297
|
+
let contents = '';
|
|
244
298
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
references.push(expression.left.property.name);
|
|
248
|
-
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
249
|
-
statement.declarations.forEach((declaration) => {
|
|
250
|
-
const { init, id } = declaration;
|
|
299
|
+
reactiveElements.forEach((element, idx) => {
|
|
300
|
+
const { effect, attributes } = element;
|
|
251
301
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
references.push(property.key.name);
|
|
259
|
-
});
|
|
260
|
-
} else {
|
|
261
|
-
// TODO we are just blindly tracking anything here.
|
|
262
|
-
// everything should ideally be mapped to actual this references, to create a strong chain of direct reactivity
|
|
263
|
-
// instead of tracking any declaration as a derived tracking attr
|
|
264
|
-
// for convenience here, we push the entire declaration here, instead of the name like for direct this references (see above)
|
|
265
|
-
references.push(declaration);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
302
|
+
if (effect?.expression) {
|
|
303
|
+
contents += `
|
|
304
|
+
this.$eff${effectsCount} = effect(() => {
|
|
305
|
+
this.$el${idx}.textContent = ${effect.expression}
|
|
306
|
+
});\n
|
|
307
|
+
`;
|
|
269
308
|
|
|
270
|
-
|
|
309
|
+
effectsCount++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (attributes.length > 0) {
|
|
313
|
+
const attributeUpdates = attributes
|
|
314
|
+
.map((attr) => {
|
|
315
|
+
return `this.$el${idx}.setAttribute('${attr.name}', this.${attr.value}.get())`;
|
|
316
|
+
})
|
|
317
|
+
.join('\n');
|
|
318
|
+
|
|
319
|
+
contents += `
|
|
320
|
+
this.$eff${effectsCount} = effect(() => {
|
|
321
|
+
${attributeUpdates}
|
|
322
|
+
});\n
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
effectsCount++;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return contents;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function generateEffectsCleanupForReactiveElements(reactiveElements) {
|
|
333
|
+
let contents = '';
|
|
334
|
+
let effectCount = 0;
|
|
335
|
+
|
|
336
|
+
reactiveElements.forEach((element) => {
|
|
337
|
+
const { effect, attributes } = element;
|
|
338
|
+
|
|
339
|
+
if (effect?.expression) {
|
|
340
|
+
contents += `
|
|
341
|
+
this.$eff${effectCount}()\n;
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
effectCount++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (attributes.length > 0) {
|
|
348
|
+
contents += `
|
|
349
|
+
this.$eff${effectCount}();
|
|
350
|
+
`;
|
|
351
|
+
effectCount += 1;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return contents;
|
|
271
356
|
}
|
|
272
357
|
|
|
273
358
|
export function parseJsx(moduleURL) {
|
|
@@ -281,14 +366,22 @@ export function parseJsx(moduleURL) {
|
|
|
281
366
|
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
282
367
|
const hasOwnObservedAttributes = undefined;
|
|
283
368
|
let inferredObservability = false;
|
|
369
|
+
// TODO: "merge" observedAttributes tracking with constructor tracking
|
|
284
370
|
let observedAttributes = [];
|
|
285
|
-
let
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
371
|
+
let constructorMembersSignals = new Map();
|
|
372
|
+
let reactiveElements = [];
|
|
373
|
+
let componentName;
|
|
374
|
+
let hasShadowRoot;
|
|
375
|
+
let hasDisconnectedCallback;
|
|
376
|
+
let tree = acorn.Parser.extend(jsx()).parse(result.code, acornParseOptions);
|
|
289
377
|
string = '';
|
|
290
378
|
|
|
291
|
-
//
|
|
379
|
+
// initial pass to get certain information before running JSX transformations (could we do this in one pass?)
|
|
380
|
+
// 1. if `inferredObservability` is set
|
|
381
|
+
// 2. get the name of the component class for `static` references
|
|
382
|
+
// 3, track observed attributes from `this` references in the template
|
|
383
|
+
// 4. track if Shadow DOM is being used
|
|
384
|
+
// 5. track if disconnectedCallback is already defined
|
|
292
385
|
walk.simple(
|
|
293
386
|
tree,
|
|
294
387
|
{
|
|
@@ -308,6 +401,84 @@ export function parseJsx(moduleURL) {
|
|
|
308
401
|
}
|
|
309
402
|
}
|
|
310
403
|
},
|
|
404
|
+
ExportDefaultDeclaration(node) {
|
|
405
|
+
const { declaration } = node;
|
|
406
|
+
|
|
407
|
+
if (
|
|
408
|
+
declaration &&
|
|
409
|
+
declaration.type === 'ClassDeclaration' &&
|
|
410
|
+
declaration.id &&
|
|
411
|
+
declaration.id.name
|
|
412
|
+
) {
|
|
413
|
+
componentName = declaration.id.name;
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
ClassDeclaration(node) {
|
|
417
|
+
// @ts-ignore
|
|
418
|
+
if (node.superClass.name === 'HTMLElement') {
|
|
419
|
+
// TODO: (good first issue) find a more AST (visitor) based way to check for this
|
|
420
|
+
// https://github.com/ProjectEvergreen/wcc/issues/258
|
|
421
|
+
hasShadowRoot =
|
|
422
|
+
moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
423
|
+
|
|
424
|
+
for (const n1 of node.body.body) {
|
|
425
|
+
if (n1.type === 'MethodDefinition') {
|
|
426
|
+
// @ts-ignore
|
|
427
|
+
const nodeName = n1.key.name;
|
|
428
|
+
if (nodeName === 'render') {
|
|
429
|
+
for (const n2 in n1.value.body.body) {
|
|
430
|
+
const n = n1.value.body.body[n2];
|
|
431
|
+
|
|
432
|
+
if (n.type === 'VariableDeclaration') {
|
|
433
|
+
observedAttributes = [
|
|
434
|
+
...observedAttributes,
|
|
435
|
+
...findThisReferences('render', n),
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
MethodDefinition(node) {
|
|
445
|
+
if (node.kind === 'method' && node?.key.name === 'disconnectedCallback') {
|
|
446
|
+
hasDisconnectedCallback = true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// @ts-ignore
|
|
450
|
+
if (
|
|
451
|
+
node.kind === 'constructor' &&
|
|
452
|
+
node?.value.type === 'FunctionExpression' &&
|
|
453
|
+
node.value.body?.type === 'BlockStatement'
|
|
454
|
+
) {
|
|
455
|
+
const root = node.value.body?.body;
|
|
456
|
+
for (const n of root) {
|
|
457
|
+
if (
|
|
458
|
+
n.type === 'ExpressionStatement' &&
|
|
459
|
+
n.expression?.type === 'AssignmentExpression' &&
|
|
460
|
+
n.expression?.operator === '=' &&
|
|
461
|
+
n.expression.left.object.type === 'ThisExpression'
|
|
462
|
+
) {
|
|
463
|
+
const { left, right } = n.expression;
|
|
464
|
+
if (
|
|
465
|
+
right.type === 'NewExpression' &&
|
|
466
|
+
right.callee?.object?.type === 'Identifier' &&
|
|
467
|
+
right.callee?.object?.name === 'Signal'
|
|
468
|
+
) {
|
|
469
|
+
const name = left.property.name;
|
|
470
|
+
const isState = right.callee?.property?.name === 'State';
|
|
471
|
+
const isComputed = right.callee?.property?.name === 'Computed';
|
|
472
|
+
|
|
473
|
+
constructorMembersSignals.set(name, {
|
|
474
|
+
isState,
|
|
475
|
+
isComputed,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
},
|
|
311
482
|
},
|
|
312
483
|
{
|
|
313
484
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
@@ -317,15 +488,201 @@ export function parseJsx(moduleURL) {
|
|
|
317
488
|
},
|
|
318
489
|
);
|
|
319
490
|
|
|
491
|
+
// we do this first to track reactivity usage before we transform the template and re-write its contents
|
|
492
|
+
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
493
|
+
// this scans for signals usage within the template and builds up a reactive list of templates + effects
|
|
494
|
+
// (could this be done during the transformation pass instead of having to generate and re-parse the module again?)
|
|
495
|
+
// TODO: recursive scanning for nested components
|
|
496
|
+
// https://github.com/ProjectEvergreen/wcc/issues/256
|
|
497
|
+
walk.simple(
|
|
498
|
+
tree,
|
|
499
|
+
{
|
|
500
|
+
MethodDefinition(node) {
|
|
501
|
+
if (node.key.name === 'render') {
|
|
502
|
+
for (const n2 in node.value.body.body) {
|
|
503
|
+
const n = node.value.body.body[n2];
|
|
504
|
+
if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
505
|
+
const rootNode = n.argument;
|
|
506
|
+
const parentTag = rootNode.openingElement.name.name;
|
|
507
|
+
const children = [];
|
|
508
|
+
let parentTemplate = '';
|
|
509
|
+
let parentHasReactiveTemplate;
|
|
510
|
+
let parentHasReactiveAttributes;
|
|
511
|
+
let parentSignals = [];
|
|
512
|
+
|
|
513
|
+
for (const child of rootNode.children) {
|
|
514
|
+
if (child.type === 'JSXText') {
|
|
515
|
+
// TODO: track top level reactivity for text nodes?
|
|
516
|
+
// https://github.com/ProjectEvergreen/wcc/issues/256
|
|
517
|
+
parentTemplate += child.raw;
|
|
518
|
+
} else if (child.type === 'JSXExpressionContainer') {
|
|
519
|
+
if (
|
|
520
|
+
child.expression?.type === 'CallExpression' &&
|
|
521
|
+
child.expression?.callee?.type === 'MemberExpression' &&
|
|
522
|
+
child.expression?.callee?.property?.name === 'get'
|
|
523
|
+
) {
|
|
524
|
+
const { object } = child.expression.callee || {};
|
|
525
|
+
parentTemplate += `$\{${object.name}}`;
|
|
526
|
+
parentSignals.push(object.name);
|
|
527
|
+
parentHasReactiveTemplate = true;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
parentHasReactiveAttributes = rootNode.openingElement.attributes.some(
|
|
531
|
+
(a) =>
|
|
532
|
+
a.value.type === 'JSXExpressionContainer' &&
|
|
533
|
+
a.value.expression?.type === 'CallExpression' &&
|
|
534
|
+
a.value.expression?.callee?.type === 'MemberExpression' &&
|
|
535
|
+
a.value.expression?.callee?.property?.name === 'get',
|
|
536
|
+
);
|
|
537
|
+
} else if (child.type === 'JSXElement') {
|
|
538
|
+
const childTag = child.openingElement.name.name;
|
|
539
|
+
|
|
540
|
+
// track children for determining correct effect selector
|
|
541
|
+
if (!children[childTag]) {
|
|
542
|
+
children[childTag] = [];
|
|
543
|
+
}
|
|
544
|
+
children[childTag].push(childTag);
|
|
545
|
+
|
|
546
|
+
// TODO: I think we are only checking for State, I think we also need to handle Computeds (by themselves) here as well
|
|
547
|
+
// TODO: should we filter the things that call .get() out against actual signals found in the constructor / JSX?
|
|
548
|
+
// https://github.com/ProjectEvergreen/wcc/issues/256
|
|
549
|
+
const hasReactiveTemplate = child.children.some(
|
|
550
|
+
(c) =>
|
|
551
|
+
c.type === 'JSXExpressionContainer' &&
|
|
552
|
+
c.expression?.type === 'CallExpression' &&
|
|
553
|
+
c.expression?.callee?.type === 'MemberExpression' &&
|
|
554
|
+
c.expression?.callee?.property?.name === 'get',
|
|
555
|
+
);
|
|
556
|
+
const hasReactiveAttributes = child.openingElement.attributes.some(
|
|
557
|
+
(a) =>
|
|
558
|
+
a.value.type === 'JSXExpressionContainer' &&
|
|
559
|
+
a.value.expression?.type === 'CallExpression' &&
|
|
560
|
+
a.value.expression?.callee?.type === 'MemberExpression' &&
|
|
561
|
+
a.value.expression?.callee?.property?.name === 'get',
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
if (hasReactiveTemplate || hasReactiveAttributes) {
|
|
565
|
+
reactiveElements.push({
|
|
566
|
+
selector: `${parentTag} > ${childTag}:nth-of-type(${children[childTag].length})`,
|
|
567
|
+
template: {},
|
|
568
|
+
attributes: [],
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (hasReactiveTemplate) {
|
|
573
|
+
const signals = [];
|
|
574
|
+
let template = '';
|
|
575
|
+
|
|
576
|
+
for (const c of child.children) {
|
|
577
|
+
if (c.type === 'JSXText') {
|
|
578
|
+
template += c.value;
|
|
579
|
+
} else if (c.type === 'JSXExpressionContainer') {
|
|
580
|
+
// TODO: handle this references?
|
|
581
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
582
|
+
const { object } = c.expression.callee || {};
|
|
583
|
+
template += `$\{${object.name}}`;
|
|
584
|
+
signals.push(object.name);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (template !== '' && signals.length > 0) {
|
|
589
|
+
const $$templ = `$$tmpl${reactiveElements.length - 1}`;
|
|
590
|
+
const staticTemplate = `static ${$$templ} = (${signals.join(',')}) => \`${template.trim()}\`;`;
|
|
591
|
+
// TODO: handle this references?
|
|
592
|
+
// https://www.github.com/ProjectEvergreen/wcc/issues/88
|
|
593
|
+
const expression = `${componentName}.${$$templ}(${signals.map((s) => `this.${s}.get()`).join(', ')});`;
|
|
594
|
+
|
|
595
|
+
reactiveElements[reactiveElements.length - 1].effect = {
|
|
596
|
+
template: staticTemplate,
|
|
597
|
+
expression,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (hasReactiveAttributes) {
|
|
603
|
+
for (const attr of child.openingElement.attributes) {
|
|
604
|
+
if (
|
|
605
|
+
attr.value.type === 'JSXExpressionContainer' &&
|
|
606
|
+
attr.value.expression?.type === 'CallExpression' &&
|
|
607
|
+
attr.value.expression?.callee?.type === 'MemberExpression' &&
|
|
608
|
+
attr.value.expression?.callee?.property?.name === 'get'
|
|
609
|
+
) {
|
|
610
|
+
const isThisExpression =
|
|
611
|
+
attr.value.expression?.callee?.object?.object?.type ===
|
|
612
|
+
'ThisExpression';
|
|
613
|
+
const value = isThisExpression
|
|
614
|
+
? attr.value.expression?.callee?.object?.property.name
|
|
615
|
+
: attr.value.expression?.callee?.object.name;
|
|
616
|
+
|
|
617
|
+
reactiveElements[reactiveElements.length - 1].attributes.push({
|
|
618
|
+
name: attr.name.name,
|
|
619
|
+
value,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (parentHasReactiveTemplate) {
|
|
628
|
+
const $$templ = `$$tmpl${reactiveElements.length}`;
|
|
629
|
+
const staticTemplate = `static ${$$templ} = (${parentSignals.join(',')}) => \`${parentTemplate.trim()}\`;`;
|
|
630
|
+
// TODO: handle this references?
|
|
631
|
+
// https://www.github.com/ProjectEvergreen/wcc/issues/88
|
|
632
|
+
const expression = `${componentName}.${$$templ}(${parentSignals.map((s) => `this.${s}.get()`).join(', ')});`;
|
|
633
|
+
|
|
634
|
+
reactiveElements.push({
|
|
635
|
+
selector: `${parentTag}:nth-of-type(1)`,
|
|
636
|
+
effect: {
|
|
637
|
+
template: staticTemplate,
|
|
638
|
+
expression,
|
|
639
|
+
},
|
|
640
|
+
attributes: [],
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
if (parentHasReactiveAttributes) {
|
|
644
|
+
for (const attr of rootNode.openingElement.attributes) {
|
|
645
|
+
if (
|
|
646
|
+
attr.value.type === 'JSXExpressionContainer' &&
|
|
647
|
+
attr.value.expression?.type === 'CallExpression' &&
|
|
648
|
+
attr.value.expression?.callee?.type === 'MemberExpression' &&
|
|
649
|
+
attr.value.expression?.callee?.property?.name === 'get'
|
|
650
|
+
) {
|
|
651
|
+
const isThisExpression =
|
|
652
|
+
attr.value.expression?.callee?.object?.object?.type === 'ThisExpression';
|
|
653
|
+
const value = isThisExpression
|
|
654
|
+
? attr.value.expression?.callee?.object?.property.name
|
|
655
|
+
: attr.value.expression?.callee?.object.name;
|
|
656
|
+
|
|
657
|
+
reactiveElements[reactiveElements.length - 1].attributes.push({
|
|
658
|
+
name: attr.name.name,
|
|
659
|
+
value,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
672
|
+
...walk.base,
|
|
673
|
+
// @ts-ignore
|
|
674
|
+
JSXElement: () => {},
|
|
675
|
+
},
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// apply all JSX transformations
|
|
320
680
|
walk.simple(
|
|
321
681
|
tree,
|
|
322
682
|
{
|
|
323
683
|
ClassDeclaration(node) {
|
|
324
684
|
// @ts-ignore
|
|
325
685
|
if (node.superClass.name === 'HTMLElement') {
|
|
326
|
-
const hasShadowRoot =
|
|
327
|
-
moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
328
|
-
|
|
329
686
|
for (const n1 of node.body.body) {
|
|
330
687
|
if (n1.type === 'MethodDefinition') {
|
|
331
688
|
// @ts-ignore
|
|
@@ -334,13 +691,7 @@ export function parseJsx(moduleURL) {
|
|
|
334
691
|
for (const n2 in n1.value.body.body) {
|
|
335
692
|
const n = n1.value.body.body[n2];
|
|
336
693
|
|
|
337
|
-
if (n.type === '
|
|
338
|
-
observedAttributes = [
|
|
339
|
-
...observedAttributes,
|
|
340
|
-
...findThisReferences('render', n),
|
|
341
|
-
];
|
|
342
|
-
// @ts-ignore
|
|
343
|
-
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
694
|
+
if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
344
695
|
const html = parseJsxElement(n.argument, moduleContents, inferredObservability);
|
|
345
696
|
const elementTree = getParse(html)(html);
|
|
346
697
|
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
@@ -366,10 +717,7 @@ export function parseJsx(moduleURL) {
|
|
|
366
717
|
}
|
|
367
718
|
`
|
|
368
719
|
: `${elementRoot}.innerHTML = \`${serializedHtml}\`;`;
|
|
369
|
-
const transformed = acorn.parse(renderHandler,
|
|
370
|
-
ecmaVersion: 'latest',
|
|
371
|
-
sourceType: 'module',
|
|
372
|
-
});
|
|
720
|
+
const transformed = acorn.parse(renderHandler, acornParseOptions);
|
|
373
721
|
|
|
374
722
|
// @ts-ignore
|
|
375
723
|
n1.value.body.body[n2] = transformed;
|
|
@@ -389,113 +737,105 @@ export function parseJsx(moduleURL) {
|
|
|
389
737
|
},
|
|
390
738
|
);
|
|
391
739
|
|
|
740
|
+
// TODO: why does this compilation run twice? logging here will output the message twice
|
|
392
741
|
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
742
|
+
// here we do the following with all the work we've done so far tracking attributes, signals, effects, etc
|
|
743
|
+
// 1. setup static template functions and observed attributes that we've tracked so far to inject into the top of the class body
|
|
744
|
+
// 2. append all effects to the end of the connectedCallback function
|
|
745
|
+
// 3. setup cache references to all elements used in effects
|
|
746
|
+
walk.simple(
|
|
747
|
+
tree,
|
|
748
|
+
{
|
|
749
|
+
ClassDeclaration(node) {
|
|
750
|
+
if (
|
|
751
|
+
node.id.name === componentName &&
|
|
752
|
+
node.type === 'ClassDeclaration' &&
|
|
753
|
+
node.body.type === 'ClassBody'
|
|
754
|
+
) {
|
|
755
|
+
// TODO: do we even need this filter?
|
|
756
|
+
const trackingAttrs = observedAttributes.filter((attr) => typeof attr === 'string');
|
|
757
|
+
const disconnectedCallbackContents = hasDisconnectedCallback
|
|
758
|
+
? ''
|
|
759
|
+
: `
|
|
760
|
+
disconnectedCallback() {
|
|
761
|
+
${generateEffectsCleanupForReactiveElements(reactiveElements)}
|
|
762
|
+
}
|
|
763
|
+
`;
|
|
764
|
+
|
|
765
|
+
// TODO: better way to determine value type, e,g. array, number, object, etc for `parseAttribute`?
|
|
766
|
+
// have to wrap these `static` calls in a class here, otherwise we can't parse them standalone w/ acorn
|
|
767
|
+
const staticContents = `
|
|
768
|
+
class Stub {
|
|
769
|
+
${reactiveElements.map((el, idx) => `$el${idx};`).join('')}
|
|
770
|
+
${reactiveElements
|
|
771
|
+
.filter((el) => el.effect?.template)
|
|
772
|
+
.map((el) => el.effect.template)
|
|
773
|
+
.join('\n')}
|
|
774
|
+
static get observedAttributes() {
|
|
775
|
+
return [${[...trackingAttrs]
|
|
776
|
+
.filter((attr) => constructorMembersSignals.get(attr)?.isState)
|
|
777
|
+
.map((attr) => `'${attr}'`)
|
|
778
|
+
.join()}]
|
|
779
|
+
}
|
|
780
|
+
static parseAttribute = (value) => value.charAt(0) === '{' || value.charAt(0) === '['
|
|
781
|
+
? JSON.parse(value)
|
|
782
|
+
: value !== '' && !isNaN(+value)
|
|
783
|
+
? parseInt(value, 10)
|
|
784
|
+
: value === 'true' || value === 'false'
|
|
785
|
+
? value === 'true' ? true : false
|
|
786
|
+
: value;
|
|
787
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
788
|
+
this[name].set(${componentName}.parseAttribute(newValue));
|
|
789
|
+
}
|
|
790
|
+
${disconnectedCallbackContents}
|
|
791
|
+
}
|
|
792
|
+
`;
|
|
405
793
|
|
|
406
|
-
|
|
407
|
-
const trackingAttrs = observedAttributes.filter((attr) => typeof attr === 'string');
|
|
408
|
-
// TODO ideally derivedAttrs would explicitly reference trackingAttrs
|
|
409
|
-
// and if there are no derivedAttrs, do not include the derivedGetters / derivedSetters code in the compiled output
|
|
410
|
-
const derivedAttrs = observedAttributes.filter((attr) => typeof attr !== 'string');
|
|
411
|
-
const derivedGetters = derivedAttrs
|
|
412
|
-
.map((attr) => {
|
|
413
|
-
return `
|
|
414
|
-
get_${attr.id.name}(${trackingAttrs.join(',')}) {
|
|
415
|
-
return ${moduleContents.slice(attr.init.start, attr.init.end)}
|
|
416
|
-
}
|
|
417
|
-
`;
|
|
418
|
-
})
|
|
419
|
-
.join('\n');
|
|
420
|
-
const derivedSetters = derivedAttrs
|
|
421
|
-
.map((attr) => {
|
|
422
|
-
const name = attr.id.name;
|
|
423
|
-
|
|
424
|
-
return `
|
|
425
|
-
const old_${name} = this.get_${name}(oldValue);
|
|
426
|
-
const new_${name} = this.get_${name}(newValue);
|
|
427
|
-
this.update('${name}', old_${name}, new_${name});
|
|
428
|
-
`;
|
|
429
|
-
})
|
|
430
|
-
.join('\n');
|
|
431
|
-
|
|
432
|
-
// TODO: better way to determine value type, e,g. array, int, object, etc?
|
|
433
|
-
// TODO: better way to test for shadowRoot presence when running querySelectorAll
|
|
434
|
-
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
435
|
-
static get observedAttributes() {
|
|
436
|
-
return [${[...trackingAttrs].map((attr) => `'${attr}'`).join()}]
|
|
437
|
-
}
|
|
794
|
+
const staticContentsTree = acorn.parse(staticContents, acornParseOptions);
|
|
438
795
|
|
|
439
|
-
|
|
440
|
-
function getValue(value) {
|
|
441
|
-
return value.charAt(0) === '{' || value.charAt(0) === '['
|
|
442
|
-
? JSON.parse(value)
|
|
443
|
-
: !isNaN(value)
|
|
444
|
-
? parseInt(value, 10)
|
|
445
|
-
: value === 'true' || value === 'false'
|
|
446
|
-
? value === 'true' ? true : false
|
|
447
|
-
: value;
|
|
448
|
-
}
|
|
449
|
-
if (newValue !== oldValue) {
|
|
450
|
-
switch(name) {
|
|
451
|
-
${trackingAttrs
|
|
452
|
-
.map((attr) => {
|
|
453
|
-
return `
|
|
454
|
-
case '${attr}':
|
|
455
|
-
this.${attr} = getValue(newValue);
|
|
456
|
-
break;
|
|
457
|
-
`;
|
|
458
|
-
})
|
|
459
|
-
.join('\n')}
|
|
796
|
+
node.body.body.unshift(...staticContentsTree.body[0].body.body);
|
|
460
797
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (el.hasAttribute(el.getAttribute(attr))) {
|
|
479
|
-
el.setAttribute(el.getAttribute(attr), newValue);
|
|
480
|
-
}
|
|
481
|
-
break;
|
|
798
|
+
},
|
|
799
|
+
MethodDefinition(node) {
|
|
800
|
+
if (node.key.name === 'connectedCallback') {
|
|
801
|
+
const root = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
802
|
+
const effectElements = reactiveElements
|
|
803
|
+
.map((el, idx) => `this.$el${idx} = ${root}.querySelector('${el.selector}');`)
|
|
804
|
+
.join('\n');
|
|
805
|
+
const effectContents = generateEffectsForReactiveElements(reactiveElements);
|
|
806
|
+
|
|
807
|
+
const effectElementsTree = acorn.parse(effectElements, acornParseOptions);
|
|
808
|
+
const effectContentsTree = acorn.parse(effectContents, acornParseOptions);
|
|
809
|
+
|
|
810
|
+
node.value.body.body = [
|
|
811
|
+
...node.value.body.body,
|
|
812
|
+
...effectElementsTree.body,
|
|
813
|
+
...effectContentsTree.body,
|
|
814
|
+
];
|
|
482
815
|
}
|
|
483
|
-
})
|
|
484
816
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
817
|
+
if (node.key.name === 'disconnectedCallback' && hasDisconnectedCallback) {
|
|
818
|
+
const effectCleanupContents =
|
|
819
|
+
generateEffectsCleanupForReactiveElements(reactiveElements);
|
|
820
|
+
const effectCleanupElementsTree = acorn.parse(effectCleanupContents, acornParseOptions);
|
|
489
821
|
|
|
490
|
-
|
|
822
|
+
node.value.body.body = [...node.value.body.body, ...effectCleanupElementsTree.body];
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
828
|
+
...walk.base,
|
|
829
|
+
// @ts-ignore
|
|
830
|
+
JSXElement: () => {},
|
|
831
|
+
},
|
|
832
|
+
);
|
|
491
833
|
|
|
492
|
-
|
|
493
|
-
`;
|
|
834
|
+
// inject WCC's effect function
|
|
835
|
+
const effectImportContents = `import { effect } from 'wc-compiler/effect'`;
|
|
836
|
+
const effectImportTree = acorn.parse(effectImportContents, acornParseOptions);
|
|
494
837
|
|
|
495
|
-
tree =
|
|
496
|
-
ecmaVersion: 'latest',
|
|
497
|
-
sourceType: 'module',
|
|
498
|
-
});
|
|
838
|
+
tree.body = [...effectImportTree.body, ...tree.body];
|
|
499
839
|
}
|
|
500
840
|
|
|
501
841
|
return tree;
|