wc-compiler 0.6.2 → 0.8.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 +144 -31
- package/package.json +11 -3
- package/src/jsx-loader.js +144 -28
- package/src/wcc.js +5 -5
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
|
|
|
@@ -27632,8 +27629,6 @@ var serialize = function(node, options) {
|
|
|
27632
27629
|
|
|
27633
27630
|
/* eslint-disable max-depth, complexity */
|
|
27634
27631
|
|
|
27635
|
-
url.pathToFileURL(`${process.cwd()}/`).href;
|
|
27636
|
-
|
|
27637
27632
|
// TODO same hack as definitions
|
|
27638
27633
|
// https://github.com/ProjectEvergreen/wcc/discussions/74
|
|
27639
27634
|
let string;
|
|
@@ -27647,7 +27642,7 @@ function getParse$1(html) {
|
|
|
27647
27642
|
}
|
|
27648
27643
|
|
|
27649
27644
|
function getParser(moduleURL) {
|
|
27650
|
-
const isJSX =
|
|
27645
|
+
const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
|
|
27651
27646
|
|
|
27652
27647
|
if (!isJSX) {
|
|
27653
27648
|
return;
|
|
@@ -27819,14 +27814,52 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
27819
27814
|
return string;
|
|
27820
27815
|
}
|
|
27821
27816
|
|
|
27817
|
+
// TODO handle if / else statements
|
|
27818
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
27819
|
+
function findThisReferences(context, statement) {
|
|
27820
|
+
const references = [];
|
|
27821
|
+
const isRenderFunctionContext = context === 'render';
|
|
27822
|
+
const { expression, type } = statement;
|
|
27823
|
+
const isConstructorThisAssignment = context === 'constructor'
|
|
27824
|
+
&& type === 'ExpressionStatement'
|
|
27825
|
+
&& expression.type === 'AssignmentExpression'
|
|
27826
|
+
&& expression.left.object.type === 'ThisExpression';
|
|
27827
|
+
|
|
27828
|
+
if (isConstructorThisAssignment) {
|
|
27829
|
+
// this.name = 'something'; // constructor
|
|
27830
|
+
references.push(expression.left.property.name);
|
|
27831
|
+
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
27832
|
+
statement.declarations.forEach(declaration => {
|
|
27833
|
+
const { init, id } = declaration;
|
|
27834
|
+
|
|
27835
|
+
if (init.object && init.object.type === 'ThisExpression') {
|
|
27836
|
+
// const { description } = this.todo;
|
|
27837
|
+
references.push(init.property.name);
|
|
27838
|
+
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
27839
|
+
// const { description } = this.todo;
|
|
27840
|
+
id.properties.forEach((property) => {
|
|
27841
|
+
references.push(property.key.name);
|
|
27842
|
+
});
|
|
27843
|
+
}
|
|
27844
|
+
});
|
|
27845
|
+
}
|
|
27846
|
+
|
|
27847
|
+
return references;
|
|
27848
|
+
}
|
|
27849
|
+
|
|
27822
27850
|
function parseJsx(moduleURL) {
|
|
27823
27851
|
const moduleContents = fs__default["default"].readFileSync(moduleURL, 'utf-8');
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27852
|
+
// would be nice if we could do this instead, so we could know ahead of time
|
|
27853
|
+
// const { inferredObservability } = await import(moduleURL);
|
|
27854
|
+
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
27855
|
+
const hasOwnObservedAttributes = undefined;
|
|
27856
|
+
let inferredObservability = false;
|
|
27857
|
+
let observedAttributes = [];
|
|
27858
|
+
let tree = Parser$2.extend(jsx()).parse(moduleContents, {
|
|
27827
27859
|
ecmaVersion: 'latest',
|
|
27828
27860
|
sourceType: 'module'
|
|
27829
27861
|
});
|
|
27862
|
+
string = '';
|
|
27830
27863
|
|
|
27831
27864
|
simple(tree, {
|
|
27832
27865
|
ClassDeclaration(node) {
|
|
@@ -27834,29 +27867,46 @@ function parseJsx(moduleURL) {
|
|
|
27834
27867
|
const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
27835
27868
|
|
|
27836
27869
|
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
|
-
|
|
27870
|
+
if (n1.type === 'MethodDefinition') {
|
|
27871
|
+
const nodeName = n1.key.name;
|
|
27872
|
+
if (nodeName === 'render') {
|
|
27873
|
+
for (const n2 in n1.value.body.body) {
|
|
27874
|
+
const n = n1.value.body.body[n2];
|
|
27875
|
+
|
|
27876
|
+
if (n.type === 'VariableDeclaration') {
|
|
27877
|
+
observedAttributes = [
|
|
27878
|
+
...observedAttributes,
|
|
27879
|
+
...findThisReferences('render', n)
|
|
27880
|
+
];
|
|
27881
|
+
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
27882
|
+
const html = parseJsxElement(n.argument, moduleContents);
|
|
27883
|
+
const elementTree = getParse$1(html)(html);
|
|
27884
|
+
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
27885
|
+
|
|
27886
|
+
applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
|
|
27887
|
+
|
|
27888
|
+
const finalHtml = serialize(elementTree);
|
|
27889
|
+
const transformed = parse$1(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
|
|
27890
|
+
ecmaVersion: 'latest',
|
|
27891
|
+
sourceType: 'module'
|
|
27892
|
+
});
|
|
27893
|
+
|
|
27894
|
+
n1.value.body.body[n2] = transformed;
|
|
27895
|
+
}
|
|
27855
27896
|
}
|
|
27856
27897
|
}
|
|
27857
27898
|
}
|
|
27858
27899
|
}
|
|
27859
27900
|
}
|
|
27901
|
+
},
|
|
27902
|
+
ExportNamedDeclaration(node) {
|
|
27903
|
+
const { declaration } = node;
|
|
27904
|
+
|
|
27905
|
+
if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
|
|
27906
|
+
if (declaration.declarations[0].id.name === 'inferredObservability') {
|
|
27907
|
+
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
27908
|
+
}
|
|
27909
|
+
}
|
|
27860
27910
|
}
|
|
27861
27911
|
}, {
|
|
27862
27912
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
@@ -27864,6 +27914,68 @@ function parseJsx(moduleURL) {
|
|
|
27864
27914
|
JSXElement: () => {}
|
|
27865
27915
|
});
|
|
27866
27916
|
|
|
27917
|
+
// TODO - signals: use constructor, render, HTML attributes? some, none, or all?
|
|
27918
|
+
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
27919
|
+
let insertPoint;
|
|
27920
|
+
for (const line of tree.body) {
|
|
27921
|
+
// test for class MyComponent vs export default class MyComponent
|
|
27922
|
+
if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
|
|
27923
|
+
const children = !line.declaration
|
|
27924
|
+
? line.body.body
|
|
27925
|
+
: line.declaration.body.body;
|
|
27926
|
+
for (const method of children) {
|
|
27927
|
+
if (method.key.name === 'constructor') {
|
|
27928
|
+
insertPoint = method.start - 1;
|
|
27929
|
+
break;
|
|
27930
|
+
}
|
|
27931
|
+
}
|
|
27932
|
+
}
|
|
27933
|
+
}
|
|
27934
|
+
|
|
27935
|
+
let newModuleContents = escodegen.generate(tree);
|
|
27936
|
+
|
|
27937
|
+
// TODO better way to determine value type?
|
|
27938
|
+
/* eslint-disable indent */
|
|
27939
|
+
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
27940
|
+
static get observedAttributes() {
|
|
27941
|
+
return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
|
|
27942
|
+
}
|
|
27943
|
+
|
|
27944
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
27945
|
+
function getValue(value) {
|
|
27946
|
+
return value.charAt(0) === '{' || value.charAt(0) === '['
|
|
27947
|
+
? JSON.parse(value)
|
|
27948
|
+
: !isNaN(value)
|
|
27949
|
+
? parseInt(value, 10)
|
|
27950
|
+
: value === 'true' || value === 'false'
|
|
27951
|
+
? value === 'true' ? true : false
|
|
27952
|
+
: value;
|
|
27953
|
+
}
|
|
27954
|
+
if (newValue !== oldValue) {
|
|
27955
|
+
switch(name) {
|
|
27956
|
+
${observedAttributes.map((attr) => {
|
|
27957
|
+
return `
|
|
27958
|
+
case '${attr}':
|
|
27959
|
+
this.${attr} = getValue(newValue);
|
|
27960
|
+
break;
|
|
27961
|
+
`;
|
|
27962
|
+
}).join('\n')}
|
|
27963
|
+
}
|
|
27964
|
+
|
|
27965
|
+
this.render();
|
|
27966
|
+
}
|
|
27967
|
+
}
|
|
27968
|
+
|
|
27969
|
+
${newModuleContents.slice(insertPoint)}
|
|
27970
|
+
`;
|
|
27971
|
+
/* eslint-enable indent */
|
|
27972
|
+
|
|
27973
|
+
tree = Parser$2.extend(jsx()).parse(newModuleContents, {
|
|
27974
|
+
ecmaVersion: 'latest',
|
|
27975
|
+
sourceType: 'module'
|
|
27976
|
+
});
|
|
27977
|
+
}
|
|
27978
|
+
|
|
27867
27979
|
return tree;
|
|
27868
27980
|
}
|
|
27869
27981
|
|
|
@@ -27933,9 +28045,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
27933
28045
|
ImportDeclaration(node) {
|
|
27934
28046
|
const specifier = node.source.value;
|
|
27935
28047
|
const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
|
|
28048
|
+
const extension = specifier.split('.').pop();
|
|
27936
28049
|
|
|
27937
28050
|
// TODO would like to decouple .jsx from the core, ideally
|
|
27938
|
-
if (!isBareSpecifier && ['
|
|
28051
|
+
if (!isBareSpecifier && ['js', 'jsx'].includes(extension)) {
|
|
27939
28052
|
const dependencyModuleURL = new URL(node.source.value, moduleURL);
|
|
27940
28053
|
|
|
27941
28054
|
registerDependencies(dependencyModuleURL, definitions, nextDepth);
|
|
@@ -28019,9 +28132,9 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
|
|
|
28019
28132
|
}
|
|
28020
28133
|
}
|
|
28021
28134
|
|
|
28022
|
-
async function renderToString(elementURL) {
|
|
28135
|
+
async function renderToString(elementURL, wrappingEntryTag = true) {
|
|
28023
28136
|
const definitions = [];
|
|
28024
|
-
const elementTagName = await getTagName(elementURL);
|
|
28137
|
+
const elementTagName = wrappingEntryTag && await getTagName(elementURL);
|
|
28025
28138
|
const isEntry = !!elementTagName;
|
|
28026
28139
|
const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry);
|
|
28027
28140
|
|
|
@@ -28030,7 +28143,7 @@ async function renderToString(elementURL) {
|
|
|
28030
28143
|
: elementInstance.innerHTML;
|
|
28031
28144
|
const elementTree = getParse(elementHtml)(elementHtml);
|
|
28032
28145
|
const finalTree = await renderComponentRoots(elementTree, definitions);
|
|
28033
|
-
const html = elementTagName ? `
|
|
28146
|
+
const html = wrappingEntryTag && elementTagName ? `
|
|
28034
28147
|
<${elementTagName}>
|
|
28035
28148
|
${serialize(finalTree)}
|
|
28036
28149
|
</${elementTagName}>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wc-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Experimental native Web Components compiler.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"main": "src/wcc.js",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"author": "Owen Buckley <owen@thegreenhouse.io>",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"Web Components",
|
|
14
|
+
"JSX",
|
|
15
|
+
"Greenwood"
|
|
16
|
+
],
|
|
12
17
|
"license": "MIT",
|
|
13
18
|
"engines": {
|
|
14
19
|
"node": ">=14"
|
|
@@ -22,12 +27,12 @@
|
|
|
22
27
|
},
|
|
23
28
|
"scripts": {
|
|
24
29
|
"clean": "rimraf ./dist",
|
|
25
|
-
"lint": "eslint \"*.*js\" \"./src/**/**/*.js*\" \"./test/**/**/*.js*\"",
|
|
30
|
+
"lint": "ls-lint && eslint \"*.*js\" \"./src/**/**/*.js*\" \"./docs/**/*.md\" \"./test/**/**/*.js*\"",
|
|
26
31
|
"develop": "concurrently \"nodemon --watch src --watch docs -e js,md,css,html,jsx ./build.js\" \"http-server ./dist --open\"",
|
|
27
32
|
"build": "node ./build.js",
|
|
28
33
|
"serve": "node ./build.js && http-server ./dist --open",
|
|
29
34
|
"start": "npm run develop",
|
|
30
|
-
"test": "mocha --exclude \"./test/cases/jsx
|
|
35
|
+
"test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
|
|
31
36
|
"test:exp": "c8 node --experimental-loader ./test-exp-loader.js ./node_modules/mocha/bin/mocha \"./test/**/**/*.spec.js\"",
|
|
32
37
|
"test:tdd": "npm run test -- --watch",
|
|
33
38
|
"test:tdd:exp": "npm run test:exp -- --watch",
|
|
@@ -42,6 +47,7 @@
|
|
|
42
47
|
"parse5": "^6.0.1"
|
|
43
48
|
},
|
|
44
49
|
"devDependencies": {
|
|
50
|
+
"@ls-lint/ls-lint": "^1.10.0",
|
|
45
51
|
"@mapbox/rehype-prism": "^0.8.0",
|
|
46
52
|
"@rollup/plugin-commonjs": "^22.0.0",
|
|
47
53
|
"@rollup/plugin-json": "^4.1.0",
|
|
@@ -50,6 +56,8 @@
|
|
|
50
56
|
"chai": "^4.3.6",
|
|
51
57
|
"concurrently": "^7.1.0",
|
|
52
58
|
"eslint": "^8.14.0",
|
|
59
|
+
"eslint-plugin-markdown": "^3.0.0",
|
|
60
|
+
"eslint-plugin-no-only-tests": "^2.6.0",
|
|
53
61
|
"http-server": "^14.1.0",
|
|
54
62
|
"jsdom": "^19.0.0",
|
|
55
63
|
"mocha": "^9.2.2",
|
package/src/jsx-loader.js
CHANGED
|
@@ -6,10 +6,7 @@ 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 = pathToFileURL(`${process.cwd()}/`).href;
|
|
13
10
|
const jsxRegex = /\.(jsx)$/;
|
|
14
11
|
|
|
15
12
|
// TODO same hack as definitions
|
|
@@ -25,7 +22,7 @@ function getParse(html) {
|
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
export function getParser(moduleURL) {
|
|
28
|
-
const isJSX =
|
|
25
|
+
const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
|
|
29
26
|
|
|
30
27
|
if (!isJSX) {
|
|
31
28
|
return;
|
|
@@ -197,14 +194,52 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
197
194
|
return string;
|
|
198
195
|
}
|
|
199
196
|
|
|
197
|
+
// TODO handle if / else statements
|
|
198
|
+
// https://github.com/ProjectEvergreen/wcc/issues/88
|
|
199
|
+
function findThisReferences(context, statement) {
|
|
200
|
+
const references = [];
|
|
201
|
+
const isRenderFunctionContext = context === 'render';
|
|
202
|
+
const { expression, type } = statement;
|
|
203
|
+
const isConstructorThisAssignment = context === 'constructor'
|
|
204
|
+
&& type === 'ExpressionStatement'
|
|
205
|
+
&& expression.type === 'AssignmentExpression'
|
|
206
|
+
&& expression.left.object.type === 'ThisExpression';
|
|
207
|
+
|
|
208
|
+
if (isConstructorThisAssignment) {
|
|
209
|
+
// this.name = 'something'; // constructor
|
|
210
|
+
references.push(expression.left.property.name);
|
|
211
|
+
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
212
|
+
statement.declarations.forEach(declaration => {
|
|
213
|
+
const { init, id } = declaration;
|
|
214
|
+
|
|
215
|
+
if (init.object && init.object.type === 'ThisExpression') {
|
|
216
|
+
// const { description } = this.todo;
|
|
217
|
+
references.push(init.property.name);
|
|
218
|
+
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
219
|
+
// const { description } = this.todo;
|
|
220
|
+
id.properties.forEach((property) => {
|
|
221
|
+
references.push(property.key.name);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return references;
|
|
228
|
+
}
|
|
229
|
+
|
|
200
230
|
export function parseJsx(moduleURL) {
|
|
201
231
|
const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
// would be nice if we could do this instead, so we could know ahead of time
|
|
233
|
+
// const { inferredObservability } = await import(moduleURL);
|
|
234
|
+
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
235
|
+
const hasOwnObservedAttributes = undefined;
|
|
236
|
+
let inferredObservability = false;
|
|
237
|
+
let observedAttributes = [];
|
|
238
|
+
let tree = acorn.Parser.extend(jsx()).parse(moduleContents, {
|
|
205
239
|
ecmaVersion: 'latest',
|
|
206
240
|
sourceType: 'module'
|
|
207
241
|
});
|
|
242
|
+
string = '';
|
|
208
243
|
|
|
209
244
|
walk.simple(tree, {
|
|
210
245
|
ClassDeclaration(node) {
|
|
@@ -212,29 +247,46 @@ export function parseJsx(moduleURL) {
|
|
|
212
247
|
const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
213
248
|
|
|
214
249
|
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
|
-
|
|
250
|
+
if (n1.type === 'MethodDefinition') {
|
|
251
|
+
const nodeName = n1.key.name;
|
|
252
|
+
if (nodeName === 'render') {
|
|
253
|
+
for (const n2 in n1.value.body.body) {
|
|
254
|
+
const n = n1.value.body.body[n2];
|
|
255
|
+
|
|
256
|
+
if (n.type === 'VariableDeclaration') {
|
|
257
|
+
observedAttributes = [
|
|
258
|
+
...observedAttributes,
|
|
259
|
+
...findThisReferences('render', n)
|
|
260
|
+
];
|
|
261
|
+
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
262
|
+
const html = parseJsxElement(n.argument, moduleContents);
|
|
263
|
+
const elementTree = getParse(html)(html);
|
|
264
|
+
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
265
|
+
|
|
266
|
+
applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
|
|
267
|
+
|
|
268
|
+
const finalHtml = serialize(elementTree);
|
|
269
|
+
const transformed = acorn.parse(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
|
|
270
|
+
ecmaVersion: 'latest',
|
|
271
|
+
sourceType: 'module'
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
n1.value.body.body[n2] = transformed;
|
|
275
|
+
}
|
|
233
276
|
}
|
|
234
277
|
}
|
|
235
278
|
}
|
|
236
279
|
}
|
|
237
280
|
}
|
|
281
|
+
},
|
|
282
|
+
ExportNamedDeclaration(node) {
|
|
283
|
+
const { declaration } = node;
|
|
284
|
+
|
|
285
|
+
if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
|
|
286
|
+
if (declaration.declarations[0].id.name === 'inferredObservability') {
|
|
287
|
+
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
238
290
|
}
|
|
239
291
|
}, {
|
|
240
292
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
@@ -242,17 +294,80 @@ export function parseJsx(moduleURL) {
|
|
|
242
294
|
JSXElement: () => {}
|
|
243
295
|
});
|
|
244
296
|
|
|
297
|
+
// TODO - signals: use constructor, render, HTML attributes? some, none, or all?
|
|
298
|
+
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
299
|
+
let insertPoint;
|
|
300
|
+
for (const line of tree.body) {
|
|
301
|
+
// test for class MyComponent vs export default class MyComponent
|
|
302
|
+
if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
|
|
303
|
+
const children = !line.declaration
|
|
304
|
+
? line.body.body
|
|
305
|
+
: line.declaration.body.body;
|
|
306
|
+
for (const method of children) {
|
|
307
|
+
if (method.key.name === 'constructor') {
|
|
308
|
+
insertPoint = method.start - 1;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let newModuleContents = escodegen.generate(tree);
|
|
316
|
+
|
|
317
|
+
// TODO better way to determine value type?
|
|
318
|
+
/* eslint-disable indent */
|
|
319
|
+
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
320
|
+
static get observedAttributes() {
|
|
321
|
+
return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
325
|
+
function getValue(value) {
|
|
326
|
+
return value.charAt(0) === '{' || value.charAt(0) === '['
|
|
327
|
+
? JSON.parse(value)
|
|
328
|
+
: !isNaN(value)
|
|
329
|
+
? parseInt(value, 10)
|
|
330
|
+
: value === 'true' || value === 'false'
|
|
331
|
+
? value === 'true' ? true : false
|
|
332
|
+
: value;
|
|
333
|
+
}
|
|
334
|
+
if (newValue !== oldValue) {
|
|
335
|
+
switch(name) {
|
|
336
|
+
${observedAttributes.map((attr) => {
|
|
337
|
+
return `
|
|
338
|
+
case '${attr}':
|
|
339
|
+
this.${attr} = getValue(newValue);
|
|
340
|
+
break;
|
|
341
|
+
`;
|
|
342
|
+
}).join('\n')}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.render();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
${newModuleContents.slice(insertPoint)}
|
|
350
|
+
`;
|
|
351
|
+
/* eslint-enable indent */
|
|
352
|
+
|
|
353
|
+
tree = acorn.Parser.extend(jsx()).parse(newModuleContents, {
|
|
354
|
+
ecmaVersion: 'latest',
|
|
355
|
+
sourceType: 'module'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
245
359
|
return tree;
|
|
246
360
|
}
|
|
247
361
|
|
|
248
362
|
// --------------
|
|
249
363
|
|
|
250
364
|
export function resolve(specifier, context, defaultResolve) {
|
|
251
|
-
const { parentURL
|
|
365
|
+
const { parentURL } = context;
|
|
252
366
|
|
|
253
367
|
if (jsxRegex.test(specifier)) {
|
|
254
368
|
return {
|
|
255
|
-
url: new URL(specifier, parentURL).href
|
|
369
|
+
url: new URL(specifier, parentURL).href,
|
|
370
|
+
shortCircuit: true
|
|
256
371
|
};
|
|
257
372
|
}
|
|
258
373
|
|
|
@@ -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);
|
|
@@ -160,9 +160,9 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
async function renderToString(elementURL) {
|
|
163
|
+
async function renderToString(elementURL, wrappingEntryTag = true) {
|
|
164
164
|
const definitions = [];
|
|
165
|
-
const elementTagName = await getTagName(elementURL);
|
|
165
|
+
const elementTagName = wrappingEntryTag && await getTagName(elementURL);
|
|
166
166
|
const isEntry = !!elementTagName;
|
|
167
167
|
const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry);
|
|
168
168
|
|
|
@@ -171,7 +171,7 @@ async function renderToString(elementURL) {
|
|
|
171
171
|
: elementInstance.innerHTML;
|
|
172
172
|
const elementTree = getParse(elementHtml)(elementHtml);
|
|
173
173
|
const finalTree = await renderComponentRoots(elementTree, definitions);
|
|
174
|
-
const html = elementTagName ? `
|
|
174
|
+
const html = wrappingEntryTag && elementTagName ? `
|
|
175
175
|
<${elementTagName}>
|
|
176
176
|
${serialize(finalTree)}
|
|
177
177
|
</${elementTagName}>
|