wc-compiler 0.6.1 → 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/wcc.dist.cjs +163 -42
- package/package.json +11 -2
- package/src/jsx-loader.js +142 -26
- package/src/wcc.js +19 -13
package/dist/wcc.dist.cjs
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var fs = require('fs');
|
|
6
|
-
var path = require('path');
|
|
7
|
-
var url = require('url');
|
|
8
6
|
|
|
9
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
8
|
|
|
@@ -27,7 +25,6 @@ function _interopNamespace(e) {
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
30
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
31
28
|
|
|
32
29
|
function noop() { }
|
|
33
30
|
|
|
@@ -86,7 +83,7 @@ class Document extends Node$1 {
|
|
|
86
83
|
return new HTMLTemplateElement();
|
|
87
84
|
|
|
88
85
|
default:
|
|
89
|
-
return new HTMLElement();
|
|
86
|
+
return new HTMLElement$1();
|
|
90
87
|
|
|
91
88
|
}
|
|
92
89
|
}
|
|
@@ -98,7 +95,7 @@ class Document extends Node$1 {
|
|
|
98
95
|
|
|
99
96
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
|
100
97
|
// EventTarget <- Node <- Element <- HTMLElement
|
|
101
|
-
class HTMLElement extends Element {
|
|
98
|
+
class HTMLElement$1 extends Element {
|
|
102
99
|
attachShadow(options) {
|
|
103
100
|
this.shadowRoot = new ShadowRoot(options);
|
|
104
101
|
|
|
@@ -129,7 +126,7 @@ class ShadowRoot extends DocumentFragment {
|
|
|
129
126
|
|
|
130
127
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement
|
|
131
128
|
// EventTarget <- Node <- Element <- HTMLElement <- HTMLTemplateElement
|
|
132
|
-
class HTMLTemplateElement extends HTMLElement {
|
|
129
|
+
class HTMLTemplateElement extends HTMLElement$1 {
|
|
133
130
|
constructor() {
|
|
134
131
|
super();
|
|
135
132
|
this.content = new DocumentFragment();
|
|
@@ -171,7 +168,7 @@ class CustomElementsRegistry {
|
|
|
171
168
|
globalThis.addEventListener = noop;
|
|
172
169
|
globalThis.document = new Document();
|
|
173
170
|
globalThis.customElements = new CustomElementsRegistry();
|
|
174
|
-
globalThis.HTMLElement = HTMLElement;
|
|
171
|
+
globalThis.HTMLElement = HTMLElement$1;
|
|
175
172
|
|
|
176
173
|
// Reserved word lists for various dialects of the language
|
|
177
174
|
|
|
@@ -27632,7 +27629,7 @@ var serialize = function(node, options) {
|
|
|
27632
27629
|
|
|
27633
27630
|
/* eslint-disable max-depth, complexity */
|
|
27634
27631
|
|
|
27635
|
-
|
|
27632
|
+
new URL(`file://${process.cwd()}/`);
|
|
27636
27633
|
|
|
27637
27634
|
// TODO same hack as definitions
|
|
27638
27635
|
// https://github.com/ProjectEvergreen/wcc/discussions/74
|
|
@@ -27647,7 +27644,7 @@ function getParse$1(html) {
|
|
|
27647
27644
|
}
|
|
27648
27645
|
|
|
27649
27646
|
function getParser(moduleURL) {
|
|
27650
|
-
const isJSX =
|
|
27647
|
+
const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
|
|
27651
27648
|
|
|
27652
27649
|
if (!isJSX) {
|
|
27653
27650
|
return;
|
|
@@ -27819,14 +27816,52 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
27819
27816
|
return string;
|
|
27820
27817
|
}
|
|
27821
27818
|
|
|
27819
|
+
// TODO handle if / else statements
|
|
27820
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
27821
|
+
function findThisReferences(context, statement) {
|
|
27822
|
+
const references = [];
|
|
27823
|
+
const isRenderFunctionContext = context === 'render';
|
|
27824
|
+
const { expression, type } = statement;
|
|
27825
|
+
const isConstructorThisAssignment = context === 'constructor'
|
|
27826
|
+
&& type === 'ExpressionStatement'
|
|
27827
|
+
&& expression.type === 'AssignmentExpression'
|
|
27828
|
+
&& expression.left.object.type === 'ThisExpression';
|
|
27829
|
+
|
|
27830
|
+
if (isConstructorThisAssignment) {
|
|
27831
|
+
// this.name = 'something'; // constructor
|
|
27832
|
+
references.push(expression.left.property.name);
|
|
27833
|
+
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
27834
|
+
statement.declarations.forEach(declaration => {
|
|
27835
|
+
const { init, id } = declaration;
|
|
27836
|
+
|
|
27837
|
+
if (init.object && init.object.type === 'ThisExpression') {
|
|
27838
|
+
// const { description } = this.todo;
|
|
27839
|
+
references.push(init.property.name);
|
|
27840
|
+
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
27841
|
+
// const { description } = this.todo;
|
|
27842
|
+
id.properties.forEach((property) => {
|
|
27843
|
+
references.push(property.key.name);
|
|
27844
|
+
});
|
|
27845
|
+
}
|
|
27846
|
+
});
|
|
27847
|
+
}
|
|
27848
|
+
|
|
27849
|
+
return references;
|
|
27850
|
+
}
|
|
27851
|
+
|
|
27822
27852
|
function parseJsx(moduleURL) {
|
|
27823
27853
|
const moduleContents = fs__default["default"].readFileSync(moduleURL, 'utf-8');
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27854
|
+
// would be nice if we could do this instead, so we could know ahead of time
|
|
27855
|
+
// const { inferredObservability } = await import(moduleURL);
|
|
27856
|
+
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
27857
|
+
const hasOwnObservedAttributes = undefined;
|
|
27858
|
+
let inferredObservability = false;
|
|
27859
|
+
let observedAttributes = [];
|
|
27860
|
+
let tree = Parser$2.extend(jsx()).parse(moduleContents, {
|
|
27827
27861
|
ecmaVersion: 'latest',
|
|
27828
27862
|
sourceType: 'module'
|
|
27829
27863
|
});
|
|
27864
|
+
string = '';
|
|
27830
27865
|
|
|
27831
27866
|
simple(tree, {
|
|
27832
27867
|
ClassDeclaration(node) {
|
|
@@ -27834,29 +27869,46 @@ function parseJsx(moduleURL) {
|
|
|
27834
27869
|
const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
27835
27870
|
|
|
27836
27871
|
for (const n1 of node.body.body) {
|
|
27837
|
-
if (n1.type === 'MethodDefinition'
|
|
27838
|
-
|
|
27839
|
-
|
|
27840
|
-
|
|
27841
|
-
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
|
|
27847
|
-
|
|
27848
|
-
|
|
27849
|
-
|
|
27850
|
-
|
|
27851
|
-
|
|
27852
|
-
|
|
27853
|
-
|
|
27854
|
-
|
|
27872
|
+
if (n1.type === 'MethodDefinition') {
|
|
27873
|
+
const nodeName = n1.key.name;
|
|
27874
|
+
if (nodeName === 'render') {
|
|
27875
|
+
for (const n2 in n1.value.body.body) {
|
|
27876
|
+
const n = n1.value.body.body[n2];
|
|
27877
|
+
|
|
27878
|
+
if (n.type === 'VariableDeclaration') {
|
|
27879
|
+
observedAttributes = [
|
|
27880
|
+
...observedAttributes,
|
|
27881
|
+
...findThisReferences('render', n)
|
|
27882
|
+
];
|
|
27883
|
+
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
27884
|
+
const html = parseJsxElement(n.argument, moduleContents);
|
|
27885
|
+
const elementTree = getParse$1(html)(html);
|
|
27886
|
+
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
27887
|
+
|
|
27888
|
+
applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
|
|
27889
|
+
|
|
27890
|
+
const finalHtml = serialize(elementTree);
|
|
27891
|
+
const transformed = parse$1(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
|
|
27892
|
+
ecmaVersion: 'latest',
|
|
27893
|
+
sourceType: 'module'
|
|
27894
|
+
});
|
|
27895
|
+
|
|
27896
|
+
n1.value.body.body[n2] = transformed;
|
|
27897
|
+
}
|
|
27855
27898
|
}
|
|
27856
27899
|
}
|
|
27857
27900
|
}
|
|
27858
27901
|
}
|
|
27859
27902
|
}
|
|
27903
|
+
},
|
|
27904
|
+
ExportNamedDeclaration(node) {
|
|
27905
|
+
const { declaration } = node;
|
|
27906
|
+
|
|
27907
|
+
if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
|
|
27908
|
+
if (declaration.declarations[0].id.name === 'inferredObservability') {
|
|
27909
|
+
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
27910
|
+
}
|
|
27911
|
+
}
|
|
27860
27912
|
}
|
|
27861
27913
|
}, {
|
|
27862
27914
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
@@ -27864,6 +27916,68 @@ function parseJsx(moduleURL) {
|
|
|
27864
27916
|
JSXElement: () => {}
|
|
27865
27917
|
});
|
|
27866
27918
|
|
|
27919
|
+
// TODO - signals: use constructor, render, HTML attributes? some, none, or all?
|
|
27920
|
+
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
27921
|
+
let insertPoint;
|
|
27922
|
+
for (const line of tree.body) {
|
|
27923
|
+
// test for class MyComponent vs export default class MyComponent
|
|
27924
|
+
if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
|
|
27925
|
+
const children = !line.declaration
|
|
27926
|
+
? line.body.body
|
|
27927
|
+
: line.declaration.body.body;
|
|
27928
|
+
for (const method of children) {
|
|
27929
|
+
if (method.key.name === 'constructor') {
|
|
27930
|
+
insertPoint = method.start - 1;
|
|
27931
|
+
break;
|
|
27932
|
+
}
|
|
27933
|
+
}
|
|
27934
|
+
}
|
|
27935
|
+
}
|
|
27936
|
+
|
|
27937
|
+
let newModuleContents = escodegen.generate(tree);
|
|
27938
|
+
|
|
27939
|
+
// TODO better way to determine value type?
|
|
27940
|
+
/* eslint-disable indent */
|
|
27941
|
+
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
27942
|
+
static get observedAttributes() {
|
|
27943
|
+
return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
|
|
27944
|
+
}
|
|
27945
|
+
|
|
27946
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
27947
|
+
function getValue(value) {
|
|
27948
|
+
return value.charAt(0) === '{' || value.charAt(0) === '['
|
|
27949
|
+
? JSON.parse(value)
|
|
27950
|
+
: !isNaN(value)
|
|
27951
|
+
? parseInt(value, 10)
|
|
27952
|
+
: value === 'true' || value === 'false'
|
|
27953
|
+
? value === 'true' ? true : false
|
|
27954
|
+
: value;
|
|
27955
|
+
}
|
|
27956
|
+
if (newValue !== oldValue) {
|
|
27957
|
+
switch(name) {
|
|
27958
|
+
${observedAttributes.map((attr) => {
|
|
27959
|
+
return `
|
|
27960
|
+
case '${attr}':
|
|
27961
|
+
this.${attr} = getValue(newValue);
|
|
27962
|
+
break;
|
|
27963
|
+
`;
|
|
27964
|
+
}).join('\n')}
|
|
27965
|
+
}
|
|
27966
|
+
|
|
27967
|
+
this.render();
|
|
27968
|
+
}
|
|
27969
|
+
}
|
|
27970
|
+
|
|
27971
|
+
${newModuleContents.slice(insertPoint)}
|
|
27972
|
+
`;
|
|
27973
|
+
/* eslint-enable indent */
|
|
27974
|
+
|
|
27975
|
+
tree = Parser$2.extend(jsx()).parse(newModuleContents, {
|
|
27976
|
+
ecmaVersion: 'latest',
|
|
27977
|
+
sourceType: 'module'
|
|
27978
|
+
});
|
|
27979
|
+
}
|
|
27980
|
+
|
|
27867
27981
|
return tree;
|
|
27868
27982
|
}
|
|
27869
27983
|
|
|
@@ -27933,9 +28047,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
27933
28047
|
ImportDeclaration(node) {
|
|
27934
28048
|
const specifier = node.source.value;
|
|
27935
28049
|
const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
|
|
28050
|
+
const extension = specifier.split('.').pop();
|
|
27936
28051
|
|
|
27937
28052
|
// TODO would like to decouple .jsx from the core, ideally
|
|
27938
|
-
if (!isBareSpecifier && ['
|
|
28053
|
+
if (!isBareSpecifier && ['js', 'jsx'].includes(extension)) {
|
|
27939
28054
|
const dependencyModuleURL = new URL(node.source.value, moduleURL);
|
|
27940
28055
|
|
|
27941
28056
|
registerDependencies(dependencyModuleURL, definitions, nextDepth);
|
|
@@ -27998,19 +28113,25 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
|
|
|
27998
28113
|
: (await (function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(t)); }); })(pathname)).default;
|
|
27999
28114
|
const dataLoader = (await (function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(t)); }); })(pathname)).getData;
|
|
28000
28115
|
const data = dataLoader ? await dataLoader() : {};
|
|
28001
|
-
const elementInstance = new element(data); // eslint-disable-line new-cap
|
|
28002
28116
|
|
|
28003
|
-
|
|
28004
|
-
elementInstance
|
|
28005
|
-
|
|
28006
|
-
if (attr.name === 'hydrate') {
|
|
28007
|
-
definitions[tagName].hydrate = attr.value;
|
|
28008
|
-
}
|
|
28009
|
-
});
|
|
28117
|
+
if (element) {
|
|
28118
|
+
const elementInstance = new element(data); // eslint-disable-line new-cap
|
|
28010
28119
|
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28120
|
+
attrs.forEach((attr) => {
|
|
28121
|
+
elementInstance.setAttribute(attr.name, attr.value);
|
|
28122
|
+
|
|
28123
|
+
if (attr.name === 'hydrate') {
|
|
28124
|
+
definitions[tagName].hydrate = attr.value;
|
|
28125
|
+
}
|
|
28126
|
+
});
|
|
28127
|
+
|
|
28128
|
+
await elementInstance.connectedCallback();
|
|
28129
|
+
|
|
28130
|
+
return elementInstance;
|
|
28131
|
+
} else {
|
|
28132
|
+
console.debug('No custom element class found for this file');
|
|
28133
|
+
return new HTMLElement();
|
|
28134
|
+
}
|
|
28014
28135
|
}
|
|
28015
28136
|
|
|
28016
28137
|
async function renderToString(elementURL) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wc-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Experimental native Web Components compiler.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/ProjectEvergreen/wcc.git"
|
|
8
|
+
},
|
|
5
9
|
"main": "src/wcc.js",
|
|
6
10
|
"type": "module",
|
|
7
11
|
"author": "Owen Buckley <owen@thegreenhouse.io>",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"Web Components",
|
|
14
|
+
"JSX",
|
|
15
|
+
"Greenwood"
|
|
16
|
+
],
|
|
8
17
|
"license": "MIT",
|
|
9
18
|
"engines": {
|
|
10
19
|
"node": ">=14"
|
|
@@ -23,7 +32,7 @@
|
|
|
23
32
|
"build": "node ./build.js",
|
|
24
33
|
"serve": "node ./build.js && http-server ./dist --open",
|
|
25
34
|
"start": "npm run develop",
|
|
26
|
-
"test": "mocha --exclude \"./test/cases/jsx
|
|
35
|
+
"test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
|
|
27
36
|
"test:exp": "c8 node --experimental-loader ./test-exp-loader.js ./node_modules/mocha/bin/mocha \"./test/**/**/*.spec.js\"",
|
|
28
37
|
"test:tdd": "npm run test -- --watch",
|
|
29
38
|
"test:tdd:exp": "npm run test:exp -- --watch",
|
package/src/jsx-loader.js
CHANGED
|
@@ -6,10 +6,8 @@ import escodegen from 'escodegen';
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import jsx from 'acorn-jsx';
|
|
8
8
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { URL, pathToFileURL } from 'url';
|
|
11
9
|
|
|
12
|
-
const baseURL =
|
|
10
|
+
const baseURL = new URL(`file://${process.cwd()}/`);
|
|
13
11
|
const jsxRegex = /\.(jsx)$/;
|
|
14
12
|
|
|
15
13
|
// TODO same hack as definitions
|
|
@@ -25,7 +23,7 @@ function getParse(html) {
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
export function getParser(moduleURL) {
|
|
28
|
-
const isJSX =
|
|
26
|
+
const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
|
|
29
27
|
|
|
30
28
|
if (!isJSX) {
|
|
31
29
|
return;
|
|
@@ -197,14 +195,52 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
197
195
|
return string;
|
|
198
196
|
}
|
|
199
197
|
|
|
198
|
+
// TODO handle if / else statements
|
|
199
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
200
|
+
function findThisReferences(context, statement) {
|
|
201
|
+
const references = [];
|
|
202
|
+
const isRenderFunctionContext = context === 'render';
|
|
203
|
+
const { expression, type } = statement;
|
|
204
|
+
const isConstructorThisAssignment = context === 'constructor'
|
|
205
|
+
&& type === 'ExpressionStatement'
|
|
206
|
+
&& expression.type === 'AssignmentExpression'
|
|
207
|
+
&& expression.left.object.type === 'ThisExpression';
|
|
208
|
+
|
|
209
|
+
if (isConstructorThisAssignment) {
|
|
210
|
+
// this.name = 'something'; // constructor
|
|
211
|
+
references.push(expression.left.property.name);
|
|
212
|
+
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
213
|
+
statement.declarations.forEach(declaration => {
|
|
214
|
+
const { init, id } = declaration;
|
|
215
|
+
|
|
216
|
+
if (init.object && init.object.type === 'ThisExpression') {
|
|
217
|
+
// const { description } = this.todo;
|
|
218
|
+
references.push(init.property.name);
|
|
219
|
+
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
220
|
+
// const { description } = this.todo;
|
|
221
|
+
id.properties.forEach((property) => {
|
|
222
|
+
references.push(property.key.name);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return references;
|
|
229
|
+
}
|
|
230
|
+
|
|
200
231
|
export function parseJsx(moduleURL) {
|
|
201
232
|
const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
233
|
+
// would be nice if we could do this instead, so we could know ahead of time
|
|
234
|
+
// const { inferredObservability } = await import(moduleURL);
|
|
235
|
+
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
236
|
+
const hasOwnObservedAttributes = undefined;
|
|
237
|
+
let inferredObservability = false;
|
|
238
|
+
let observedAttributes = [];
|
|
239
|
+
let tree = acorn.Parser.extend(jsx()).parse(moduleContents, {
|
|
205
240
|
ecmaVersion: 'latest',
|
|
206
241
|
sourceType: 'module'
|
|
207
242
|
});
|
|
243
|
+
string = '';
|
|
208
244
|
|
|
209
245
|
walk.simple(tree, {
|
|
210
246
|
ClassDeclaration(node) {
|
|
@@ -212,29 +248,46 @@ export function parseJsx(moduleURL) {
|
|
|
212
248
|
const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
213
249
|
|
|
214
250
|
for (const n1 of node.body.body) {
|
|
215
|
-
if (n1.type === 'MethodDefinition'
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
251
|
+
if (n1.type === 'MethodDefinition') {
|
|
252
|
+
const nodeName = n1.key.name;
|
|
253
|
+
if (nodeName === 'render') {
|
|
254
|
+
for (const n2 in n1.value.body.body) {
|
|
255
|
+
const n = n1.value.body.body[n2];
|
|
256
|
+
|
|
257
|
+
if (n.type === 'VariableDeclaration') {
|
|
258
|
+
observedAttributes = [
|
|
259
|
+
...observedAttributes,
|
|
260
|
+
...findThisReferences('render', n)
|
|
261
|
+
];
|
|
262
|
+
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
263
|
+
const html = parseJsxElement(n.argument, moduleContents);
|
|
264
|
+
const elementTree = getParse(html)(html);
|
|
265
|
+
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
266
|
+
|
|
267
|
+
applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
|
|
268
|
+
|
|
269
|
+
const finalHtml = serialize(elementTree);
|
|
270
|
+
const transformed = acorn.parse(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
|
|
271
|
+
ecmaVersion: 'latest',
|
|
272
|
+
sourceType: 'module'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
n1.value.body.body[n2] = transformed;
|
|
276
|
+
}
|
|
233
277
|
}
|
|
234
278
|
}
|
|
235
279
|
}
|
|
236
280
|
}
|
|
237
281
|
}
|
|
282
|
+
},
|
|
283
|
+
ExportNamedDeclaration(node) {
|
|
284
|
+
const { declaration } = node;
|
|
285
|
+
|
|
286
|
+
if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
|
|
287
|
+
if (declaration.declarations[0].id.name === 'inferredObservability') {
|
|
288
|
+
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
238
291
|
}
|
|
239
292
|
}, {
|
|
240
293
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
@@ -242,6 +295,68 @@ export function parseJsx(moduleURL) {
|
|
|
242
295
|
JSXElement: () => {}
|
|
243
296
|
});
|
|
244
297
|
|
|
298
|
+
// TODO - signals: use constructor, render, HTML attributes? some, none, or all?
|
|
299
|
+
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
300
|
+
let insertPoint;
|
|
301
|
+
for (const line of tree.body) {
|
|
302
|
+
// test for class MyComponent vs export default class MyComponent
|
|
303
|
+
if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
|
|
304
|
+
const children = !line.declaration
|
|
305
|
+
? line.body.body
|
|
306
|
+
: line.declaration.body.body;
|
|
307
|
+
for (const method of children) {
|
|
308
|
+
if (method.key.name === 'constructor') {
|
|
309
|
+
insertPoint = method.start - 1;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let newModuleContents = escodegen.generate(tree);
|
|
317
|
+
|
|
318
|
+
// TODO better way to determine value type?
|
|
319
|
+
/* eslint-disable indent */
|
|
320
|
+
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
321
|
+
static get observedAttributes() {
|
|
322
|
+
return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
326
|
+
function getValue(value) {
|
|
327
|
+
return value.charAt(0) === '{' || value.charAt(0) === '['
|
|
328
|
+
? JSON.parse(value)
|
|
329
|
+
: !isNaN(value)
|
|
330
|
+
? parseInt(value, 10)
|
|
331
|
+
: value === 'true' || value === 'false'
|
|
332
|
+
? value === 'true' ? true : false
|
|
333
|
+
: value;
|
|
334
|
+
}
|
|
335
|
+
if (newValue !== oldValue) {
|
|
336
|
+
switch(name) {
|
|
337
|
+
${observedAttributes.map((attr) => {
|
|
338
|
+
return `
|
|
339
|
+
case '${attr}':
|
|
340
|
+
this.${attr} = getValue(newValue);
|
|
341
|
+
break;
|
|
342
|
+
`;
|
|
343
|
+
}).join('\n')}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this.render();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
${newModuleContents.slice(insertPoint)}
|
|
351
|
+
`;
|
|
352
|
+
/* eslint-enable indent */
|
|
353
|
+
|
|
354
|
+
tree = acorn.Parser.extend(jsx()).parse(newModuleContents, {
|
|
355
|
+
ecmaVersion: 'latest',
|
|
356
|
+
sourceType: 'module'
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
245
360
|
return tree;
|
|
246
361
|
}
|
|
247
362
|
|
|
@@ -265,7 +380,8 @@ export async function load(url, context, defaultLoad) {
|
|
|
265
380
|
|
|
266
381
|
return {
|
|
267
382
|
format: 'module',
|
|
268
|
-
source: escodegen.generate(jsFromJsx)
|
|
383
|
+
source: escodegen.generate(jsFromJsx),
|
|
384
|
+
shortCircuit: true
|
|
269
385
|
};
|
|
270
386
|
}
|
|
271
387
|
|
package/src/wcc.js
CHANGED
|
@@ -8,7 +8,6 @@ import escodegen from 'escodegen';
|
|
|
8
8
|
import { getParser, parseJsx } from './jsx-loader.js';
|
|
9
9
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
10
10
|
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
11
|
|
|
13
12
|
function getParse(html) {
|
|
14
13
|
return html.indexOf('<html>') >= 0 || html.indexOf('<body>') >= 0 || html.indexOf('<head>') >= 0
|
|
@@ -74,9 +73,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
74
73
|
ImportDeclaration(node) {
|
|
75
74
|
const specifier = node.source.value;
|
|
76
75
|
const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
|
|
76
|
+
const extension = specifier.split('.').pop();
|
|
77
77
|
|
|
78
78
|
// TODO would like to decouple .jsx from the core, ideally
|
|
79
|
-
if (!isBareSpecifier && ['
|
|
79
|
+
if (!isBareSpecifier && ['js', 'jsx'].includes(extension)) {
|
|
80
80
|
const dependencyModuleURL = new URL(node.source.value, moduleURL);
|
|
81
81
|
|
|
82
82
|
registerDependencies(dependencyModuleURL, definitions, nextDepth);
|
|
@@ -139,19 +139,25 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
|
|
|
139
139
|
: (await import(pathname)).default;
|
|
140
140
|
const dataLoader = (await import(pathname)).getData;
|
|
141
141
|
const data = dataLoader ? await dataLoader() : {};
|
|
142
|
-
const elementInstance = new element(data); // eslint-disable-line new-cap
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
elementInstance
|
|
143
|
+
if (element) {
|
|
144
|
+
const elementInstance = new element(data); // eslint-disable-line new-cap
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
attrs.forEach((attr) => {
|
|
147
|
+
elementInstance.setAttribute(attr.name, attr.value);
|
|
148
|
+
|
|
149
|
+
if (attr.name === 'hydrate') {
|
|
150
|
+
definitions[tagName].hydrate = attr.value;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await elementInstance.connectedCallback();
|
|
155
|
+
|
|
156
|
+
return elementInstance;
|
|
157
|
+
} else {
|
|
158
|
+
console.debug('No custom element class found for this file');
|
|
159
|
+
return new HTMLElement();
|
|
160
|
+
}
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
async function renderToString(elementURL) {
|