wc-compiler 0.13.0 → 0.15.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 +15358 -277
- package/package.json +5 -4
- package/src/jsx-loader.js +6 -1
- package/src/ts-loader.js +32 -0
- package/src/wcc.js +90 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wc-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Experimental native Web Components compiler.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"docs:dev": "concurrently \"nodemon --watch src --watch docs -e js,md,css,html,jsx ./build.js\" \"http-server ./dist --open\"",
|
|
33
33
|
"docs:build": "node ./build.js",
|
|
34
34
|
"docs:serve": "npm run clean && npm run docs:build && http-server ./dist --open",
|
|
35
|
-
"sandbox": "npm run clean && concurrently \"nodemon --loader ./test-exp-loader.js --watch src --watch sandbox -e js,md,css,html,jsx ./sandbox.js\" \"http-server ./dist --open\" \"livereload ./dist\"",
|
|
35
|
+
"sandbox": "npm run clean && concurrently \"nodemon --loader ./test-exp-loader.js --watch src --watch sandbox -e js,md,css,html,jsx,ts ./sandbox.js\" \"http-server ./dist --open\" \"livereload ./dist\"",
|
|
36
36
|
"start": "npm run docs:serve",
|
|
37
|
-
"test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
|
|
37
|
+
"test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/ts*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
|
|
38
38
|
"test:exp": "c8 node --loader ./test-exp-loader.js ./node_modules/mocha/bin/mocha \"./test/**/**/*.spec.js\"",
|
|
39
39
|
"test:tdd": "npm run test -- --watch",
|
|
40
40
|
"test:tdd:exp": "npm run test:exp -- --watch",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"acorn": "^8.7.0",
|
|
48
48
|
"acorn-import-attributes": "^1.9.5",
|
|
49
49
|
"acorn-walk": "^8.2.0",
|
|
50
|
-
"parse5": "^6.0.1"
|
|
50
|
+
"parse5": "^6.0.1",
|
|
51
|
+
"sucrase": "^3.35.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@babel/core": "^7.24.4",
|
package/src/jsx-loader.js
CHANGED
|
@@ -8,6 +8,7 @@ import jsx from '@projectevergreen/acorn-jsx-esm';
|
|
|
8
8
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
9
9
|
// Need an acorn plugin for now - https://github.com/ProjectEvergreen/greenwood/issues/1218
|
|
10
10
|
import { importAttributes } from 'acorn-import-attributes';
|
|
11
|
+
import { transform } from 'sucrase';
|
|
11
12
|
|
|
12
13
|
const jsxRegex = /\.(jsx)$/;
|
|
13
14
|
|
|
@@ -232,13 +233,17 @@ function findThisReferences(context, statement) {
|
|
|
232
233
|
|
|
233
234
|
export function parseJsx(moduleURL) {
|
|
234
235
|
const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
|
|
236
|
+
const result = transform(moduleContents, {
|
|
237
|
+
transforms: ['typescript', 'jsx'],
|
|
238
|
+
jsxRuntime: 'preserve'
|
|
239
|
+
});
|
|
235
240
|
// would be nice if we could do this instead, so we could know ahead of time
|
|
236
241
|
// const { inferredObservability } = await import(moduleURL);
|
|
237
242
|
// however, this requires making parseJsx async, but WCC acorn walking is done sync
|
|
238
243
|
const hasOwnObservedAttributes = undefined;
|
|
239
244
|
let inferredObservability = false;
|
|
240
245
|
let observedAttributes = [];
|
|
241
|
-
let tree = acorn.Parser.extend(jsx(), importAttributes).parse(
|
|
246
|
+
let tree = acorn.Parser.extend(jsx(), importAttributes).parse(result.code, {
|
|
242
247
|
ecmaVersion: 'latest',
|
|
243
248
|
sourceType: 'module'
|
|
244
249
|
});
|
package/src/ts-loader.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { transform } from 'sucrase';
|
|
3
|
+
|
|
4
|
+
const tsRegex = /\.(ts)$/;
|
|
5
|
+
|
|
6
|
+
export function resolve(specifier, context, defaultResolve) {
|
|
7
|
+
const { parentURL } = context;
|
|
8
|
+
|
|
9
|
+
if (tsRegex.test(specifier)) {
|
|
10
|
+
return {
|
|
11
|
+
url: new URL(specifier, parentURL).href,
|
|
12
|
+
shortCircuit: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return defaultResolve(specifier, context, defaultResolve);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function load(url, context, defaultLoad) {
|
|
20
|
+
if (tsRegex.test(url)) {
|
|
21
|
+
const contents = await fs.readFile(new URL(url), 'utf-8');
|
|
22
|
+
const result = transform(contents, { transforms: ['typescript'] });
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
format: 'module',
|
|
26
|
+
shortCircuit: true,
|
|
27
|
+
source: result.code
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return defaultLoad(url, context, defaultLoad);
|
|
32
|
+
}
|
package/src/wcc.js
CHANGED
|
@@ -9,8 +9,27 @@ import { getParser, parseJsx } from './jsx-loader.js';
|
|
|
9
9
|
import { parse, parseFragment, serialize } from 'parse5';
|
|
10
10
|
// Need an acorn plugin for now - https://github.com/ProjectEvergreen/greenwood/issues/1218
|
|
11
11
|
import { importAttributes } from 'acorn-import-attributes';
|
|
12
|
+
import { transform } from 'sucrase';
|
|
12
13
|
import fs from 'fs';
|
|
13
14
|
|
|
15
|
+
// https://developer.mozilla.org/en-US/docs/Glossary/Void_element
|
|
16
|
+
const VOID_ELEMENTS = [
|
|
17
|
+
'area',
|
|
18
|
+
'base',
|
|
19
|
+
'br',
|
|
20
|
+
'col',
|
|
21
|
+
'embed',
|
|
22
|
+
'hr',
|
|
23
|
+
'img',
|
|
24
|
+
'input',
|
|
25
|
+
'link',
|
|
26
|
+
'meta',
|
|
27
|
+
'param', // deprecated
|
|
28
|
+
'source',
|
|
29
|
+
'track',
|
|
30
|
+
'wbr'
|
|
31
|
+
];
|
|
32
|
+
|
|
14
33
|
function getParse(html) {
|
|
15
34
|
return html.indexOf('<html>') >= 0 || html.indexOf('<body>') >= 0 || html.indexOf('<head>') >= 0
|
|
16
35
|
? parse
|
|
@@ -32,17 +51,26 @@ async function renderComponentRoots(tree, definitions) {
|
|
|
32
51
|
|
|
33
52
|
if (definitions[tagName]) {
|
|
34
53
|
const { moduleURL } = definitions[tagName];
|
|
35
|
-
const elementInstance = await initializeCustomElement(moduleURL, tagName, node
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
const elementInstance = await initializeCustomElement(moduleURL, tagName, node, definitions);
|
|
55
|
+
|
|
56
|
+
if (elementInstance) {
|
|
57
|
+
const hasShadow = elementInstance.shadowRoot;
|
|
58
|
+
const elementHtml = hasShadow
|
|
59
|
+
? elementInstance.getInnerHTML({ includeShadowRoots: true })
|
|
60
|
+
: elementInstance.innerHTML;
|
|
61
|
+
const elementTree = parseFragment(elementHtml);
|
|
62
|
+
const hasLight = elementTree.childNodes > 0;
|
|
63
|
+
|
|
64
|
+
node.childNodes = node.childNodes.length === 0 && hasLight && !hasShadow
|
|
65
|
+
? elementTree.childNodes
|
|
66
|
+
: hasShadow
|
|
67
|
+
? [...elementTree.childNodes, ...node.childNodes]
|
|
68
|
+
: elementTree.childNodes;
|
|
69
|
+
} else {
|
|
70
|
+
console.warn(`WARNING: customElement <${tagName}> detected but not serialized. You may not have exported it.`);
|
|
71
|
+
}
|
|
44
72
|
} else {
|
|
45
|
-
console.warn(`WARNING: customElement <${tagName}> is not defined. You may not have imported it
|
|
73
|
+
console.warn(`WARNING: customElement <${tagName}> is not defined. You may not have imported it.`);
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
|
|
@@ -61,6 +89,10 @@ async function renderComponentRoots(tree, definitions) {
|
|
|
61
89
|
|
|
62
90
|
function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
63
91
|
const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
|
|
92
|
+
const result = transform(moduleContents, {
|
|
93
|
+
transforms: ['typescript', 'jsx'],
|
|
94
|
+
jsxRuntime: 'preserve'
|
|
95
|
+
});
|
|
64
96
|
const nextDepth = depth += 1;
|
|
65
97
|
const customParser = getParser(moduleURL);
|
|
66
98
|
const parser = customParser ? customParser.parser : acorn.Parser;
|
|
@@ -68,7 +100,7 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
68
100
|
...walk.base
|
|
69
101
|
};
|
|
70
102
|
|
|
71
|
-
walk.simple(parser.extend(importAttributes).parse(
|
|
103
|
+
walk.simple(parser.extend(importAttributes).parse(result.code, {
|
|
72
104
|
ecmaVersion: 'latest',
|
|
73
105
|
sourceType: 'module'
|
|
74
106
|
}), {
|
|
@@ -77,8 +109,9 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
77
109
|
const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
|
|
78
110
|
const extension = specifier.split('.').pop();
|
|
79
111
|
|
|
80
|
-
//
|
|
81
|
-
|
|
112
|
+
// would like to decouple .jsx from the core, ideally
|
|
113
|
+
// https://github.com/ProjectEvergreen/wcc/issues/122
|
|
114
|
+
if (!isBareSpecifier && ['js', 'jsx', 'ts'].includes(extension)) {
|
|
82
115
|
const dependencyModuleURL = new URL(node.source.value, moduleURL);
|
|
83
116
|
|
|
84
117
|
registerDependencies(dependencyModuleURL, definitions, nextDepth);
|
|
@@ -107,6 +140,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
107
140
|
|
|
108
141
|
async function getTagName(moduleURL) {
|
|
109
142
|
const moduleContents = await fs.promises.readFile(moduleURL, 'utf-8');
|
|
143
|
+
const result = transform(moduleContents, {
|
|
144
|
+
transforms: ['typescript', 'jsx'],
|
|
145
|
+
jsxRuntime: 'preserve'
|
|
146
|
+
});
|
|
110
147
|
const customParser = getParser(moduleURL);
|
|
111
148
|
const parser = customParser ? customParser.parser : acorn.Parser;
|
|
112
149
|
const config = customParser ? customParser.config : {
|
|
@@ -114,7 +151,7 @@ async function getTagName(moduleURL) {
|
|
|
114
151
|
};
|
|
115
152
|
let tagName;
|
|
116
153
|
|
|
117
|
-
walk.simple(parser.extend(importAttributes).parse(
|
|
154
|
+
walk.simple(parser.extend(importAttributes).parse(result.code, {
|
|
118
155
|
ecmaVersion: 'latest',
|
|
119
156
|
sourceType: 'module'
|
|
120
157
|
}), {
|
|
@@ -128,7 +165,41 @@ async function getTagName(moduleURL) {
|
|
|
128
165
|
return tagName;
|
|
129
166
|
}
|
|
130
167
|
|
|
131
|
-
|
|
168
|
+
function renderLightDomChildren(childNodes, iHTML = '') {
|
|
169
|
+
let innerHTML = iHTML;
|
|
170
|
+
|
|
171
|
+
childNodes.forEach((child) => {
|
|
172
|
+
const { nodeName, attrs = [], value } = child;
|
|
173
|
+
|
|
174
|
+
if (nodeName !== '#text') {
|
|
175
|
+
innerHTML += `<${nodeName}`;
|
|
176
|
+
|
|
177
|
+
if (attrs.length > 0) {
|
|
178
|
+
attrs.forEach(attr => {
|
|
179
|
+
innerHTML += ` ${attr.name}="${attr.value}"`;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
innerHTML += '>';
|
|
184
|
+
|
|
185
|
+
if (child.childNodes.length > 0) {
|
|
186
|
+
innerHTML = renderLightDomChildren(child.childNodes, innerHTML);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
innerHTML += VOID_ELEMENTS.includes(nodeName)
|
|
190
|
+
? ''
|
|
191
|
+
: `</${nodeName}>`;
|
|
192
|
+
} else if (nodeName === '#text') {
|
|
193
|
+
innerHTML += value;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return innerHTML;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function initializeCustomElement(elementURL, tagName, node = {}, definitions = [], isEntry, props = {}) {
|
|
201
|
+
const { attrs = [], childNodes = [] } = node;
|
|
202
|
+
|
|
132
203
|
if (!tagName) {
|
|
133
204
|
const depth = isEntry ? 1 : 0;
|
|
134
205
|
registerDependencies(elementURL, definitions, depth);
|
|
@@ -148,6 +219,9 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
|
|
|
148
219
|
if (element) {
|
|
149
220
|
const elementInstance = new element(data); // eslint-disable-line new-cap
|
|
150
221
|
|
|
222
|
+
// support for HTML (Light DOM) Web Components
|
|
223
|
+
elementInstance.innerHTML = renderLightDomChildren(childNodes);
|
|
224
|
+
|
|
151
225
|
attrs.forEach((attr) => {
|
|
152
226
|
elementInstance.setAttribute(attr.name, attr.value);
|
|
153
227
|
|
|
@@ -197,7 +271,7 @@ async function renderFromHTML(html, elements = []) {
|
|
|
197
271
|
const definitions = [];
|
|
198
272
|
|
|
199
273
|
for (const url of elements) {
|
|
200
|
-
|
|
274
|
+
registerDependencies(url, definitions, 1);
|
|
201
275
|
}
|
|
202
276
|
|
|
203
277
|
const elementTree = getParse(html)(html);
|