wc-compiler 0.18.0 → 0.19.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/README.md +57 -52
- package/package.json +19 -13
- package/src/dom-shim.js +23 -17
- package/src/index.d.ts +19 -11
- package/src/jsx-loader.js +215 -101
- package/src/jsx-runtime.ts +1 -1
- package/src/jsx.d.ts +1 -1
- package/src/register.js +1 -1
- package/src/wcc.js +119 -82
package/README.md
CHANGED
|
@@ -8,63 +8,68 @@
|
|
|
8
8
|
[](https://nodejs.org/en/about/previous-releases")
|
|
9
9
|
[](https://www.greenwoodjs.dev/discord/)
|
|
10
10
|
|
|
11
|
-
> _Experimental Web Components compiler.
|
|
11
|
+
> _Experimental Web Components compiler. It's Web Components all the way down!_ 🐢
|
|
12
12
|
|
|
13
13
|
## How It Works
|
|
14
14
|
|
|
15
15
|
1. Write a Web Component
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const template = document.createElement('template');
|
|
19
|
+
|
|
20
|
+
template.innerHTML = `
|
|
21
|
+
<style>
|
|
22
|
+
.footer {
|
|
23
|
+
color: white;
|
|
24
|
+
background-color: #192a27;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
|
|
28
|
+
<footer class="footer">
|
|
29
|
+
<h4>My Blog © ${new Date().getFullYear()}</h4>
|
|
30
|
+
</footer>
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
class Footer extends HTMLElement {
|
|
34
|
+
connectedCallback() {
|
|
35
|
+
if (!this.shadowRoot) {
|
|
36
|
+
this.attachShadow({ mode: 'open' });
|
|
37
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default Footer;
|
|
43
|
+
|
|
44
|
+
customElements.define('wcc-footer', Footer);
|
|
45
|
+
```
|
|
46
|
+
|
|
45
47
|
1. Run it through the compiler
|
|
46
|
-
```js
|
|
47
|
-
import { renderToString } from 'wc-compiler';
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
```js
|
|
50
|
+
import { renderToString } from 'wc-compiler';
|
|
51
|
+
|
|
52
|
+
const { html } = await renderToString(new URL('./path/to/component.js', import.meta.url));
|
|
53
|
+
```
|
|
54
|
+
|
|
51
55
|
1. Get HTML!
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<wcc-footer>
|
|
59
|
+
<template shadowrootmode="open">
|
|
60
|
+
<style>
|
|
61
|
+
.footer {
|
|
62
|
+
color: white;
|
|
63
|
+
background-color: #192a27;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
|
|
67
|
+
<footer class="footer">
|
|
68
|
+
<h4>My Blog © 2022</h4>
|
|
69
|
+
</footer>
|
|
70
|
+
</template>
|
|
71
|
+
</wcc-footer>
|
|
72
|
+
```
|
|
68
73
|
|
|
69
74
|
## Installation
|
|
70
75
|
|
|
@@ -80,6 +85,6 @@ See our [website](https://merry-caramel-524e61.netlify.app/) for API docs and ex
|
|
|
80
85
|
|
|
81
86
|
## Motivation
|
|
82
87
|
|
|
83
|
-
**WCC** is not a static site generator, framework or bundler.
|
|
88
|
+
**WCC** is not a static site generator, framework or bundler. It is designed with the intent of being able to produce raw HTML from standards compliant Web Components and easily integrated _into_ a site generator or framework, like [**Greenwood**](https://www.greenwoodjs.dev). The Project Evergreen team also maintains similar integrations for [**Eleventy**](https://github.com/ProjectEvergreen/eleventy-plugin-wcc/) and [**Astro**](https://github.com/ProjectEvergreen/astro-wcc).
|
|
84
89
|
|
|
85
|
-
In addition, **WCC** hopes to provide a surface area to explore patterns around [streaming](https://github.com/ProjectEvergreen/wcc/issues/5), [serverless and edge rendering](https://github.com/thescientist13/web-components-at-the-edge), and
|
|
90
|
+
In addition, **WCC** hopes to provide a surface area to explore patterns around [streaming](https://github.com/ProjectEvergreen/wcc/issues/5), [serverless and edge rendering](https://github.com/thescientist13/web-components-at-the-edge), and acting as a test bed for the [Web Components Community Groups](https://github.com/webcomponents-cg)'s discussions around community protocols, like [hydration](https://github.com/ProjectEvergreen/wcc/issues/3).
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wc-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Experimental native Web Components compiler.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/ProjectEvergreen/wcc.git"
|
|
7
|
+
"url": "git+https://github.com/ProjectEvergreen/wcc.git"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "src/wcc.js",
|
|
11
11
|
"types": "./src/index.d.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
14
|
+
"types": "./src/index.d.ts",
|
|
15
|
+
"import": "./src/wcc.js"
|
|
16
16
|
},
|
|
17
17
|
"./register": "./src/register.js",
|
|
18
18
|
"./src/jsx-loader.js": "./src/jsx-loader.js",
|
|
@@ -37,8 +37,12 @@
|
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"clean": "rimraf ./dist",
|
|
40
|
-
"lint": "
|
|
40
|
+
"lint": "npm run lint:js && npm run lint:ls",
|
|
41
|
+
"lint:js": "eslint",
|
|
42
|
+
"lint:ls": "ls-lint",
|
|
41
43
|
"lint:types": "tsc",
|
|
44
|
+
"format": "prettier . --write",
|
|
45
|
+
"format:check": "prettier . --check",
|
|
42
46
|
"docs:dev": "concurrently \"nodemon --watch src --watch docs -e js,md,css,html,jsx,ts,tsx ./build.js\" \"http-server ./dist --open\"",
|
|
43
47
|
"docs:build": "node ./build.js",
|
|
44
48
|
"docs:serve": "npm run clean && npm run docs:build && http-server ./dist --open",
|
|
@@ -47,7 +51,8 @@
|
|
|
47
51
|
"test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/ts*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
|
|
48
52
|
"test:jsx": "c8 node --import ./test-register.js --experimental-strip-types ./node_modules/mocha/bin/mocha \"./test/**/**/*.spec.js\"",
|
|
49
53
|
"test:tdd": "npm run test -- --watch",
|
|
50
|
-
"test:tdd:jsx": "npm run test:jsx -- --watch"
|
|
54
|
+
"test:tdd:jsx": "npm run test:jsx -- --watch",
|
|
55
|
+
"prepare": "husky"
|
|
51
56
|
},
|
|
52
57
|
"dependencies": {
|
|
53
58
|
"@projectevergreen/acorn-jsx-esm": "~0.1.0",
|
|
@@ -58,25 +63,26 @@
|
|
|
58
63
|
"sucrase": "^3.35.0"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
|
-
"@
|
|
62
|
-
"@
|
|
63
|
-
"@babel/plugin-syntax-import-assertions": "^7.25.7",
|
|
64
|
-
"@eslint/js": "^9.11.1",
|
|
65
|
-
"@ls-lint/ls-lint": "^1.10.0",
|
|
66
|
+
"@eslint/js": "^9.39.1",
|
|
67
|
+
"@ls-lint/ls-lint": "^2.3.1",
|
|
66
68
|
"@mapbox/rehype-prism": "^0.8.0",
|
|
67
69
|
"@types/mocha": "^10.0.10",
|
|
68
70
|
"@types/node": "^22.13.4",
|
|
69
71
|
"c8": "^7.11.2",
|
|
70
72
|
"chai": "^4.3.6",
|
|
71
73
|
"concurrently": "^7.1.0",
|
|
72
|
-
"eslint": "^9.
|
|
73
|
-
"eslint-
|
|
74
|
+
"eslint": "^9.39.1",
|
|
75
|
+
"eslint-config-prettier": "^10.1.8",
|
|
76
|
+
"eslint-plugin-no-only-tests": "^3.3.0",
|
|
74
77
|
"globals": "^15.10.0",
|
|
75
78
|
"http-server": "^14.1.0",
|
|
79
|
+
"husky": "^9.1.7",
|
|
76
80
|
"jsdom": "^19.0.0",
|
|
81
|
+
"lint-staged": "^16.2.6",
|
|
77
82
|
"livereload": "^0.9.3",
|
|
78
83
|
"mocha": "^9.2.2",
|
|
79
84
|
"nodemon": "^2.0.15",
|
|
85
|
+
"prettier": "^3.6.2",
|
|
80
86
|
"prismjs": "^1.28.0",
|
|
81
87
|
"rehype-autolink-headings": "^6.1.1",
|
|
82
88
|
"rehype-raw": "^6.1.1",
|
package/src/dom-shim.js
CHANGED
|
@@ -46,18 +46,20 @@ function getParse5ElementDefaults(element, tagName) {
|
|
|
46
46
|
nodeName: tagName,
|
|
47
47
|
tagName: tagName,
|
|
48
48
|
namespaceURI: 'http://www.w3.org/1999/xhtml',
|
|
49
|
-
...(tagName === 'template'
|
|
49
|
+
...(tagName === 'template'
|
|
50
|
+
? { content: { nodeName: '#document-fragment', childNodes: [] } }
|
|
51
|
+
: {}),
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
function noop() {
|
|
55
|
+
function noop() {}
|
|
54
56
|
|
|
55
57
|
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet
|
|
56
58
|
class CSSStyleSheet {
|
|
57
|
-
insertRule() {
|
|
58
|
-
deleteRule() {
|
|
59
|
-
replace() {
|
|
60
|
-
replaceSync() {
|
|
59
|
+
insertRule() {}
|
|
60
|
+
deleteRule() {}
|
|
61
|
+
replace() {}
|
|
62
|
+
replaceSync() {}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
|
@@ -132,7 +134,7 @@ class Node extends EventTarget {
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
return this.childNodes
|
|
135
|
-
.map((child) => child.nodeName === '#text' ? child.value : child.textContent)
|
|
137
|
+
.map((child) => (child.nodeName === '#text' ? child.value : child.textContent))
|
|
136
138
|
.join('');
|
|
137
139
|
}
|
|
138
140
|
|
|
@@ -163,7 +165,9 @@ class Element extends Node {
|
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
getHTML({ serializableShadowRoots = false }) {
|
|
166
|
-
return this.shadowRoot && serializableShadowRoots && this.shadowRoot.serializable
|
|
168
|
+
return this.shadowRoot && serializableShadowRoots && this.shadowRoot.serializable
|
|
169
|
+
? this.shadowRoot.innerHTML
|
|
170
|
+
: '';
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
get innerHTML() {
|
|
@@ -172,7 +176,8 @@ class Element extends Node {
|
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
set innerHTML(html) {
|
|
175
|
-
(this.nodeName === 'template' ? this.content : this).childNodes =
|
|
179
|
+
(this.nodeName === 'template' ? this.content : this).childNodes =
|
|
180
|
+
getParse(html)(html).childNodes;
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
hasAttribute(name) {
|
|
@@ -198,16 +203,13 @@ class Element extends Node {
|
|
|
198
203
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document
|
|
199
204
|
// EventTarget <- Node <- Document
|
|
200
205
|
class Document extends Node {
|
|
201
|
-
|
|
202
206
|
createElement(tagName) {
|
|
203
207
|
switch (tagName) {
|
|
204
|
-
|
|
205
208
|
case 'template':
|
|
206
209
|
return new HTMLTemplateElement();
|
|
207
210
|
|
|
208
211
|
default:
|
|
209
212
|
return new HTMLElement(tagName);
|
|
210
|
-
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
|
|
@@ -223,12 +225,12 @@ class HTMLElement extends Element {
|
|
|
223
225
|
super();
|
|
224
226
|
Object.assign(this, getParse5ElementDefaults(this, tagName));
|
|
225
227
|
}
|
|
226
|
-
connectedCallback() {
|
|
228
|
+
connectedCallback() {}
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
// https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
|
|
230
232
|
// EventTarget <- Node <- DocumentFragment
|
|
231
|
-
class DocumentFragment extends Node {
|
|
233
|
+
class DocumentFragment extends Node {}
|
|
232
234
|
|
|
233
235
|
// https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
|
|
234
236
|
// EventTarget <- Node <- DocumentFragment <- ShadowRoot
|
|
@@ -241,11 +243,15 @@ class ShadowRoot extends DocumentFragment {
|
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
get innerHTML() {
|
|
244
|
-
return this.childNodes?.[0]?.content?.childNodes
|
|
246
|
+
return this.childNodes?.[0]?.content?.childNodes
|
|
247
|
+
? serialize({ childNodes: this.childNodes[0].content.childNodes })
|
|
248
|
+
: '';
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
set innerHTML(html) {
|
|
248
|
-
this.childNodes = getParse(html)(
|
|
252
|
+
this.childNodes = getParse(html)(
|
|
253
|
+
`<template shadowrootmode="${this.mode}">${html}</template>`,
|
|
254
|
+
).childNodes;
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
257
|
|
|
@@ -290,4 +296,4 @@ globalThis.document = globalThis.document ?? new Document();
|
|
|
290
296
|
globalThis.customElements = globalThis.customElements ?? new CustomElementsRegistry();
|
|
291
297
|
globalThis.HTMLElement = globalThis.HTMLElement ?? HTMLElement;
|
|
292
298
|
globalThis.DocumentFragment = globalThis.DocumentFragment ?? DocumentFragment;
|
|
293
|
-
globalThis.CSSStyleSheet = globalThis.CSSStyleSheet ?? CSSStyleSheet;
|
|
299
|
+
globalThis.CSSStyleSheet = globalThis.CSSStyleSheet ?? CSSStyleSheet;
|
package/src/index.d.ts
CHANGED
|
@@ -2,21 +2,29 @@ export type Metadata = {
|
|
|
2
2
|
[key: string]: {
|
|
3
3
|
instanceName: string;
|
|
4
4
|
moduleURL: URL;
|
|
5
|
-
isEntry: boolean
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
isEntry: boolean;
|
|
6
|
+
source: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
8
9
|
|
|
9
|
-
export type renderToString = (
|
|
10
|
+
export type renderToString = (
|
|
11
|
+
elementURL: URL,
|
|
12
|
+
wrappingEntryTag?: boolean,
|
|
13
|
+
props?: any,
|
|
14
|
+
) => Promise<{
|
|
10
15
|
html: string;
|
|
11
|
-
metadata: Metadata
|
|
12
|
-
}
|
|
16
|
+
metadata: Metadata;
|
|
17
|
+
}>;
|
|
13
18
|
|
|
14
|
-
export type renderFromHTML = (
|
|
19
|
+
export type renderFromHTML = (
|
|
20
|
+
html: string,
|
|
21
|
+
elementURLs: URL[],
|
|
22
|
+
) => Promise<{
|
|
15
23
|
html: string;
|
|
16
|
-
metadata: Metadata
|
|
17
|
-
}
|
|
24
|
+
metadata: Metadata;
|
|
25
|
+
}>;
|
|
18
26
|
|
|
19
|
-
declare module
|
|
27
|
+
declare module 'wc-compiler' {
|
|
20
28
|
export const renderToString: renderToString;
|
|
21
29
|
export const renderFromHTML: renderFromHTML;
|
|
22
|
-
}
|
|
30
|
+
}
|
package/src/jsx-loader.js
CHANGED
|
@@ -39,8 +39,8 @@ export function getParser(moduleURL) {
|
|
|
39
39
|
config: {
|
|
40
40
|
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
41
41
|
...walk.base,
|
|
42
|
-
JSXElement: () => {}
|
|
43
|
-
}
|
|
42
|
+
JSXElement: () => {},
|
|
43
|
+
},
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -57,7 +57,9 @@ function applyDomDepthSubstitutions(tree, currentDepth = 1, hasShadowRoot = fals
|
|
|
57
57
|
const { value } = attrs[attr];
|
|
58
58
|
|
|
59
59
|
if (value.indexOf('__this__.') >= 0) {
|
|
60
|
-
const root = hasShadowRoot
|
|
60
|
+
const root = hasShadowRoot
|
|
61
|
+
? '.getRootNode().host'
|
|
62
|
+
: `${'.parentElement'.repeat(currentDepth)}`;
|
|
61
63
|
|
|
62
64
|
node.attrs[attr].value = value.replace(/__this__/g, `this${root}`);
|
|
63
65
|
}
|
|
@@ -75,7 +77,7 @@ function applyDomDepthSubstitutions(tree, currentDepth = 1, hasShadowRoot = fals
|
|
|
75
77
|
return tree;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
function parseJsxElement(element, moduleContents = '') {
|
|
80
|
+
function parseJsxElement(element, moduleContents = '', inferredObservability = false) {
|
|
79
81
|
try {
|
|
80
82
|
const { type } = element;
|
|
81
83
|
|
|
@@ -122,8 +124,14 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
122
124
|
|
|
123
125
|
if (left.object.type === 'ThisExpression') {
|
|
124
126
|
if (left.property.type === 'Identifier') {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
if (inferredObservability) {
|
|
128
|
+
// very naive (fine grained?) reactivity
|
|
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
|
+
}
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
}
|
|
@@ -139,17 +147,29 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
139
147
|
// xxx={allTodos.length} >
|
|
140
148
|
const { value } = attribute;
|
|
141
149
|
const { expression } = value;
|
|
150
|
+
const expressionType = expression.type;
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
switch (expressionType) {
|
|
153
|
+
case 'Literal':
|
|
154
|
+
string += ` ${name}=${expression.raw}`;
|
|
155
|
+
break;
|
|
156
|
+
case 'Identifier':
|
|
157
|
+
string += ` ${name}=$\{${expression.name}}`;
|
|
158
|
+
break;
|
|
159
|
+
case 'MemberExpression':
|
|
160
|
+
if (expression.object.type === 'Identifier') {
|
|
161
|
+
if (expression.property.type === 'Identifier') {
|
|
162
|
+
string += ` ${name}=$\{${expression.object.name}.${expression.property.name}}`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
break;
|
|
145
168
|
}
|
|
146
169
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
string += ` ${name}=$\{${expression.object.name}.${expression.property.name}}`;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
170
|
+
// only apply this when dealing with `this` references
|
|
171
|
+
if (inferredObservability) {
|
|
172
|
+
string += ` data-wcc-${expression.name}="${name}" data-wcc-ins="attr"`;
|
|
153
173
|
}
|
|
154
174
|
}
|
|
155
175
|
} else {
|
|
@@ -159,10 +179,12 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
159
179
|
}
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
string += openingElement.selfClosing ? '/>' : '>';
|
|
182
|
+
string += openingElement.selfClosing ? ' />' : '>';
|
|
163
183
|
|
|
164
184
|
if (element.children.length > 0) {
|
|
165
|
-
element.children.forEach(child =>
|
|
185
|
+
element.children.forEach((child) =>
|
|
186
|
+
parseJsxElement(child, moduleContents, inferredObservability),
|
|
187
|
+
);
|
|
166
188
|
}
|
|
167
189
|
|
|
168
190
|
string += `</${tagName}>`;
|
|
@@ -177,6 +199,13 @@ function parseJsxElement(element, moduleContents = '') {
|
|
|
177
199
|
|
|
178
200
|
if (type === 'Identifier') {
|
|
179
201
|
// 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">`;
|
|
180
209
|
string += `$\{${element.expression.name}}`;
|
|
181
210
|
} else if (type === 'MemberExpression') {
|
|
182
211
|
const { object } = element.expression.object;
|
|
@@ -207,26 +236,33 @@ function findThisReferences(context, statement) {
|
|
|
207
236
|
const references = [];
|
|
208
237
|
const isRenderFunctionContext = context === 'render';
|
|
209
238
|
const { expression, type } = statement;
|
|
210
|
-
const isConstructorThisAssignment =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
const isConstructorThisAssignment =
|
|
240
|
+
context === 'constructor' &&
|
|
241
|
+
type === 'ExpressionStatement' &&
|
|
242
|
+
expression.type === 'AssignmentExpression' &&
|
|
243
|
+
expression.left.object.type === 'ThisExpression';
|
|
214
244
|
|
|
215
245
|
if (isConstructorThisAssignment) {
|
|
216
246
|
// this.name = 'something'; // constructor
|
|
217
247
|
references.push(expression.left.property.name);
|
|
218
248
|
} else if (isRenderFunctionContext && type === 'VariableDeclaration') {
|
|
219
|
-
statement.declarations.forEach(declaration => {
|
|
249
|
+
statement.declarations.forEach((declaration) => {
|
|
220
250
|
const { init, id } = declaration;
|
|
221
251
|
|
|
222
252
|
if (init.object && init.object.type === 'ThisExpression') {
|
|
223
253
|
// const { description } = this.todo;
|
|
224
254
|
references.push(init.property.name);
|
|
225
255
|
} else if (init.type === 'ThisExpression' && id && id.properties) {
|
|
226
|
-
// const { description } = this
|
|
256
|
+
// const { id, description } = this;
|
|
227
257
|
id.properties.forEach((property) => {
|
|
228
258
|
references.push(property.key.name);
|
|
229
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);
|
|
230
266
|
}
|
|
231
267
|
});
|
|
232
268
|
}
|
|
@@ -238,7 +274,7 @@ export function parseJsx(moduleURL) {
|
|
|
238
274
|
const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
|
|
239
275
|
const result = transform(moduleContents, {
|
|
240
276
|
transforms: ['typescript', 'jsx'],
|
|
241
|
-
jsxRuntime: 'preserve'
|
|
277
|
+
jsxRuntime: 'preserve',
|
|
242
278
|
});
|
|
243
279
|
// would be nice if we could do this instead, so we could know ahead of time
|
|
244
280
|
// const { inferredObservability } = await import(moduleURL);
|
|
@@ -248,45 +284,77 @@ export function parseJsx(moduleURL) {
|
|
|
248
284
|
let observedAttributes = [];
|
|
249
285
|
let tree = acorn.Parser.extend(jsx()).parse(result.code, {
|
|
250
286
|
ecmaVersion: 'latest',
|
|
251
|
-
sourceType: 'module'
|
|
287
|
+
sourceType: 'module',
|
|
252
288
|
});
|
|
253
289
|
string = '';
|
|
254
290
|
|
|
255
|
-
|
|
256
|
-
|
|
291
|
+
// TODO: would be nice to do this one pass, but first we need to know if `inferredObservability` is set first
|
|
292
|
+
walk.simple(
|
|
293
|
+
tree,
|
|
294
|
+
{
|
|
295
|
+
ExportNamedDeclaration(node) {
|
|
296
|
+
const { declaration } = node;
|
|
297
|
+
|
|
298
|
+
if (
|
|
299
|
+
declaration &&
|
|
300
|
+
declaration.type === 'VariableDeclaration' &&
|
|
301
|
+
declaration.kind === 'const' &&
|
|
302
|
+
declaration.declarations.length === 1
|
|
303
|
+
) {
|
|
304
|
+
// @ts-ignore
|
|
305
|
+
if (declaration.declarations[0].id.name === 'inferredObservability') {
|
|
306
|
+
// @ts-ignore
|
|
307
|
+
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
314
|
+
...walk.base,
|
|
257
315
|
// @ts-ignore
|
|
258
|
-
|
|
259
|
-
|
|
316
|
+
JSXElement: () => {},
|
|
317
|
+
},
|
|
318
|
+
);
|
|
260
319
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
320
|
+
walk.simple(
|
|
321
|
+
tree,
|
|
322
|
+
{
|
|
323
|
+
ClassDeclaration(node) {
|
|
324
|
+
// @ts-ignore
|
|
325
|
+
if (node.superClass.name === 'HTMLElement') {
|
|
326
|
+
const hasShadowRoot =
|
|
327
|
+
moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
|
|
328
|
+
|
|
329
|
+
for (const n1 of node.body.body) {
|
|
330
|
+
if (n1.type === 'MethodDefinition') {
|
|
331
|
+
// @ts-ignore
|
|
332
|
+
const nodeName = n1.key.name;
|
|
333
|
+
if (nodeName === 'render') {
|
|
334
|
+
for (const n2 in n1.value.body.body) {
|
|
335
|
+
const n = n1.value.body.body[n2];
|
|
336
|
+
|
|
337
|
+
if (n.type === 'VariableDeclaration') {
|
|
338
|
+
observedAttributes = [
|
|
339
|
+
...observedAttributes,
|
|
340
|
+
...findThisReferences('render', n),
|
|
341
|
+
];
|
|
342
|
+
// @ts-ignore
|
|
343
|
+
} else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
|
|
344
|
+
const html = parseJsxElement(n.argument, moduleContents, inferredObservability);
|
|
345
|
+
const elementTree = getParse(html)(html);
|
|
346
|
+
const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
|
|
347
|
+
|
|
348
|
+
applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
|
|
349
|
+
|
|
350
|
+
const serializedHtml = serialize(elementTree);
|
|
351
|
+
// we have to Shadow DOM use cases here
|
|
352
|
+
// 1. No shadowRoot, so we attachShadow and append the template
|
|
353
|
+
// 2. If there is root from the attachShadow signal, so we just need to inject innerHTML, say in an htmx
|
|
354
|
+
// could / should we do something else instead of .innerHTML
|
|
355
|
+
// https://github.com/ProjectEvergreen/wcc/issues/138
|
|
356
|
+
const renderHandler = hasShadowRoot
|
|
357
|
+
? `
|
|
290
358
|
const template = document.createElement('template');
|
|
291
359
|
template.innerHTML = \`${serializedHtml}\`;
|
|
292
360
|
|
|
@@ -297,57 +365,75 @@ export function parseJsx(moduleURL) {
|
|
|
297
365
|
this.shadowRoot.innerHTML = template.innerHTML;
|
|
298
366
|
}
|
|
299
367
|
`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
368
|
+
: `${elementRoot}.innerHTML = \`${serializedHtml}\`;`;
|
|
369
|
+
const transformed = acorn.parse(renderHandler, {
|
|
370
|
+
ecmaVersion: 'latest',
|
|
371
|
+
sourceType: 'module',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// @ts-ignore
|
|
375
|
+
n1.value.body.body[n2] = transformed;
|
|
376
|
+
}
|
|
308
377
|
}
|
|
309
378
|
}
|
|
310
379
|
}
|
|
311
380
|
}
|
|
312
381
|
}
|
|
313
|
-
}
|
|
382
|
+
},
|
|
314
383
|
},
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}, {
|
|
327
|
-
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
328
|
-
...walk.base,
|
|
329
|
-
// @ts-ignore
|
|
330
|
-
JSXElement: () => {}
|
|
331
|
-
});
|
|
384
|
+
{
|
|
385
|
+
// https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
|
|
386
|
+
...walk.base,
|
|
387
|
+
// @ts-ignore
|
|
388
|
+
JSXElement: () => {},
|
|
389
|
+
},
|
|
390
|
+
);
|
|
332
391
|
|
|
333
|
-
// TODO - signals: use constructor, render, HTML attributes? some, none, or all?
|
|
334
392
|
if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
|
|
335
393
|
let insertPoint;
|
|
336
394
|
for (const line of tree.body) {
|
|
337
|
-
// test for class MyComponent vs export default class MyComponent
|
|
395
|
+
// TODO: test for class MyComponent vs export default class MyComponent
|
|
338
396
|
// @ts-ignore
|
|
339
|
-
if (
|
|
397
|
+
if (
|
|
398
|
+
line.type === 'ClassDeclaration' ||
|
|
399
|
+
(line.declaration && line.declaration.type) === 'ClassDeclaration'
|
|
400
|
+
) {
|
|
340
401
|
// @ts-ignore
|
|
341
402
|
insertPoint = line.declaration.body.start + 1;
|
|
342
403
|
}
|
|
343
404
|
}
|
|
344
405
|
|
|
345
406
|
let newModuleContents = generate(tree);
|
|
346
|
-
|
|
347
|
-
// TODO
|
|
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
|
|
348
434
|
newModuleContents = `${newModuleContents.slice(0, insertPoint)}
|
|
349
435
|
static get observedAttributes() {
|
|
350
|
-
return [${[...
|
|
436
|
+
return [${[...trackingAttrs].map((attr) => `'${attr}'`).join()}]
|
|
351
437
|
}
|
|
352
438
|
|
|
353
439
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -362,25 +448,53 @@ export function parseJsx(moduleURL) {
|
|
|
362
448
|
}
|
|
363
449
|
if (newValue !== oldValue) {
|
|
364
450
|
switch(name) {
|
|
365
|
-
${
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
451
|
+
${trackingAttrs
|
|
452
|
+
.map((attr) => {
|
|
453
|
+
return `
|
|
454
|
+
case '${attr}':
|
|
455
|
+
this.${attr} = getValue(newValue);
|
|
456
|
+
break;
|
|
457
|
+
`;
|
|
458
|
+
})
|
|
459
|
+
.join('\n')}
|
|
372
460
|
}
|
|
461
|
+
this.update(name, oldValue, newValue);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
373
464
|
|
|
374
|
-
|
|
465
|
+
update(name, oldValue, newValue) {
|
|
466
|
+
const attr = \`data-wcc-\${name}\`;
|
|
467
|
+
const selector = \`[\${attr}]\`;
|
|
468
|
+
|
|
469
|
+
(this?.shadowRoot || this).querySelectorAll(selector).forEach((el) => {
|
|
470
|
+
// handle empty strings as a value for the purposes of attribute change detection
|
|
471
|
+
const needle = oldValue === '' ? '' : oldValue ?? el.getAttribute(attr);
|
|
472
|
+
|
|
473
|
+
switch(el.getAttribute('data-wcc-ins')) {
|
|
474
|
+
case 'text':
|
|
475
|
+
el.textContent = el.textContent.replace(needle, newValue);
|
|
476
|
+
break;
|
|
477
|
+
case 'attr':
|
|
478
|
+
if (el.hasAttribute(el.getAttribute(attr))) {
|
|
479
|
+
el.setAttribute(el.getAttribute(attr), newValue);
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
if ([${[...trackingAttrs].map((attr) => `'${attr}'`).join()}].includes(name)) {
|
|
486
|
+
${derivedSetters}
|
|
375
487
|
}
|
|
376
488
|
}
|
|
377
489
|
|
|
490
|
+
${derivedGetters}
|
|
491
|
+
|
|
378
492
|
${newModuleContents.slice(insertPoint)}
|
|
379
493
|
`;
|
|
380
494
|
|
|
381
495
|
tree = acorn.Parser.extend(jsx()).parse(newModuleContents, {
|
|
382
496
|
ecmaVersion: 'latest',
|
|
383
|
-
sourceType: 'module'
|
|
497
|
+
sourceType: 'module',
|
|
384
498
|
});
|
|
385
499
|
}
|
|
386
500
|
|
|
@@ -395,7 +509,7 @@ export function resolve(specifier, context, defaultResolve) {
|
|
|
395
509
|
if (jsxRegex.test(specifier) || tsxRegex.test(specifier)) {
|
|
396
510
|
return {
|
|
397
511
|
url: new URL(specifier, parentURL).href,
|
|
398
|
-
shortCircuit: true
|
|
512
|
+
shortCircuit: true,
|
|
399
513
|
};
|
|
400
514
|
}
|
|
401
515
|
|
|
@@ -409,9 +523,9 @@ export async function load(url, context, defaultLoad) {
|
|
|
409
523
|
return {
|
|
410
524
|
format: 'module',
|
|
411
525
|
source: generate(jsFromJsx),
|
|
412
|
-
shortCircuit: true
|
|
526
|
+
shortCircuit: true,
|
|
413
527
|
};
|
|
414
528
|
}
|
|
415
529
|
|
|
416
530
|
return defaultLoad(url, context, defaultLoad);
|
|
417
|
-
}
|
|
531
|
+
}
|
package/src/jsx-runtime.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import './jsx.d.ts';
|
|
1
|
+
import './jsx.d.ts';
|
package/src/jsx.d.ts
CHANGED
package/src/register.js
CHANGED
package/src/wcc.js
CHANGED
|
@@ -12,9 +12,14 @@ import fs from 'fs';
|
|
|
12
12
|
function isCustomElementDefinitionNode(node) {
|
|
13
13
|
const { expression } = node;
|
|
14
14
|
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
return (
|
|
16
|
+
expression.type === 'CallExpression' &&
|
|
17
|
+
expression.callee &&
|
|
18
|
+
expression.callee.object &&
|
|
19
|
+
expression.callee.property &&
|
|
20
|
+
expression.callee.object.name === 'customElements' &&
|
|
21
|
+
expression.callee.property.name === 'define'
|
|
22
|
+
);
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
async function renderComponentRoots(tree, definitions) {
|
|
@@ -24,7 +29,12 @@ async function renderComponentRoots(tree, definitions) {
|
|
|
24
29
|
|
|
25
30
|
if (definitions[tagName]) {
|
|
26
31
|
const { moduleURL } = definitions[tagName];
|
|
27
|
-
const elementInstance = await initializeCustomElement(
|
|
32
|
+
const elementInstance = await initializeCustomElement(
|
|
33
|
+
moduleURL,
|
|
34
|
+
tagName,
|
|
35
|
+
node,
|
|
36
|
+
definitions,
|
|
37
|
+
);
|
|
28
38
|
|
|
29
39
|
if (elementInstance) {
|
|
30
40
|
const hasShadow = elementInstance.shadowRoot;
|
|
@@ -33,10 +43,14 @@ async function renderComponentRoots(tree, definitions) {
|
|
|
33
43
|
? [...elementInstance.shadowRoot.childNodes, ...node.childNodes]
|
|
34
44
|
: elementInstance.childNodes;
|
|
35
45
|
} else {
|
|
36
|
-
console.warn(
|
|
46
|
+
console.warn(
|
|
47
|
+
`WARNING: customElement <${tagName}> detected but not serialized. You may not have exported it.`,
|
|
48
|
+
);
|
|
37
49
|
}
|
|
38
50
|
} else {
|
|
39
|
-
console.warn(
|
|
51
|
+
console.warn(
|
|
52
|
+
`WARNING: customElement <${tagName}> is not defined. You may not have imported it.`,
|
|
53
|
+
);
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
attrs.forEach((attr) => {
|
|
@@ -44,7 +58,6 @@ async function renderComponentRoots(tree, definitions) {
|
|
|
44
58
|
definitions[tagName].hydrate = attr.value;
|
|
45
59
|
}
|
|
46
60
|
});
|
|
47
|
-
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
if (node.childNodes && node.childNodes.length > 0) {
|
|
@@ -71,53 +84,60 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
|
|
|
71
84
|
jsxRuntime: 'automatic',
|
|
72
85
|
production: true,
|
|
73
86
|
});
|
|
74
|
-
const nextDepth = depth += 1;
|
|
87
|
+
const nextDepth = (depth += 1);
|
|
75
88
|
const customParser = getParser(moduleURL);
|
|
76
89
|
const parser = customParser ? customParser.parser : acorn.Parser;
|
|
77
|
-
const config = customParser
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
const config = customParser
|
|
91
|
+
? customParser.config
|
|
92
|
+
: {
|
|
93
|
+
...walk.base,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
walk.simple(
|
|
97
|
+
parser.parse(result.code, {
|
|
98
|
+
ecmaVersion: 'latest',
|
|
99
|
+
sourceType: 'module',
|
|
100
|
+
}),
|
|
101
|
+
{
|
|
102
|
+
ImportDeclaration(node) {
|
|
103
|
+
const specifier = node.source.value;
|
|
104
|
+
|
|
105
|
+
if (typeof specifier === 'string') {
|
|
106
|
+
const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
|
|
107
|
+
const extension = typeof specifier === 'string' ? specifier.split('.').pop() : '';
|
|
108
|
+
|
|
109
|
+
// would like to decouple .jsx from the core, ideally
|
|
110
|
+
// https://github.com/ProjectEvergreen/wcc/issues/122
|
|
111
|
+
if (!isBareSpecifier && ['js', 'jsx', 'ts', 'tsx'].includes(extension)) {
|
|
112
|
+
const dependencyModuleURL = new URL(specifier, moduleURL);
|
|
113
|
+
|
|
114
|
+
registerDependencies(dependencyModuleURL, definitions, nextDepth);
|
|
115
|
+
}
|
|
98
116
|
}
|
|
99
|
-
}
|
|
117
|
+
},
|
|
118
|
+
ExpressionStatement(node) {
|
|
119
|
+
if (isCustomElementDefinitionNode(node)) {
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
const { arguments: args } = node.expression;
|
|
122
|
+
const tagName =
|
|
123
|
+
args[0].type === 'Literal'
|
|
124
|
+
? args[0].value // single and double quotes
|
|
125
|
+
: args[0].quasis[0].value.raw; // template literal
|
|
126
|
+
const tree = parseJsx(moduleURL);
|
|
127
|
+
const isEntry = nextDepth - 1 === 1;
|
|
128
|
+
|
|
129
|
+
definitions[tagName] = {
|
|
130
|
+
instanceName: args[1].name,
|
|
131
|
+
moduleURL,
|
|
132
|
+
source: generate(tree),
|
|
133
|
+
url: moduleURL,
|
|
134
|
+
isEntry,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
},
|
|
100
138
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
const { arguments: args } = node.expression;
|
|
105
|
-
const tagName = args[0].type === 'Literal'
|
|
106
|
-
? args[0].value // single and double quotes
|
|
107
|
-
: args[0].quasis[0].value.raw; // template literal
|
|
108
|
-
const tree = parseJsx(moduleURL);
|
|
109
|
-
const isEntry = nextDepth - 1 === 1;
|
|
110
|
-
|
|
111
|
-
definitions[tagName] = {
|
|
112
|
-
instanceName: args[1].name,
|
|
113
|
-
moduleURL,
|
|
114
|
-
source: generate(tree),
|
|
115
|
-
url: moduleURL,
|
|
116
|
-
isEntry
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}, config);
|
|
139
|
+
config,
|
|
140
|
+
);
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
async function getTagName(moduleURL) {
|
|
@@ -129,28 +149,40 @@ async function getTagName(moduleURL) {
|
|
|
129
149
|
});
|
|
130
150
|
const customParser = getParser(moduleURL);
|
|
131
151
|
const parser = customParser ? customParser.parser : acorn.Parser;
|
|
132
|
-
const config = customParser
|
|
133
|
-
|
|
134
|
-
|
|
152
|
+
const config = customParser
|
|
153
|
+
? customParser.config
|
|
154
|
+
: {
|
|
155
|
+
...walk.base,
|
|
156
|
+
};
|
|
135
157
|
let tagName;
|
|
136
158
|
|
|
137
|
-
walk.simple(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
walk.simple(
|
|
160
|
+
parser.parse(result.code, {
|
|
161
|
+
ecmaVersion: 'latest',
|
|
162
|
+
sourceType: 'module',
|
|
163
|
+
}),
|
|
164
|
+
{
|
|
165
|
+
ExpressionStatement(node) {
|
|
166
|
+
if (isCustomElementDefinitionNode(node)) {
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
tagName = node.expression.arguments[0].value;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
config,
|
|
173
|
+
);
|
|
148
174
|
|
|
149
175
|
return tagName;
|
|
150
176
|
}
|
|
151
177
|
|
|
152
|
-
async function initializeCustomElement(
|
|
153
|
-
|
|
178
|
+
async function initializeCustomElement(
|
|
179
|
+
elementURL,
|
|
180
|
+
tagName,
|
|
181
|
+
node = {},
|
|
182
|
+
definitions = {},
|
|
183
|
+
isEntry,
|
|
184
|
+
props = {},
|
|
185
|
+
) {
|
|
154
186
|
if (!tagName) {
|
|
155
187
|
const depth = isEntry ? 1 : 0;
|
|
156
188
|
registerDependencies(elementURL, definitions, depth);
|
|
@@ -173,9 +205,16 @@ async function initializeCustomElement(elementURL, tagName, node = {}, definitio
|
|
|
173
205
|
|
|
174
206
|
async function renderToString(elementURL, wrappingEntryTag = true, props = {}) {
|
|
175
207
|
const definitions = {};
|
|
176
|
-
const elementTagName = wrappingEntryTag && await getTagName(elementURL);
|
|
208
|
+
const elementTagName = wrappingEntryTag && (await getTagName(elementURL));
|
|
177
209
|
const isEntry = !!elementTagName;
|
|
178
|
-
const elementInstance = await initializeCustomElement(
|
|
210
|
+
const elementInstance = await initializeCustomElement(
|
|
211
|
+
elementURL,
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
definitions,
|
|
215
|
+
isEntry,
|
|
216
|
+
props,
|
|
217
|
+
);
|
|
179
218
|
|
|
180
219
|
let html;
|
|
181
220
|
|
|
@@ -186,28 +225,29 @@ async function renderToString(elementURL, wrappingEntryTag = true, props = {}) {
|
|
|
186
225
|
|
|
187
226
|
await renderComponentRoots(
|
|
188
227
|
elementInstance.shadowRoot
|
|
189
|
-
?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
228
|
+
? {
|
|
229
|
+
nodeName: '#document-fragment',
|
|
230
|
+
childNodes: [elementInstance],
|
|
231
|
+
}
|
|
194
232
|
: elementInstance,
|
|
195
|
-
definitions
|
|
233
|
+
definitions,
|
|
196
234
|
);
|
|
197
235
|
|
|
198
|
-
html =
|
|
236
|
+
html =
|
|
237
|
+
wrappingEntryTag && elementTagName
|
|
238
|
+
? `
|
|
199
239
|
<${elementTagName}>
|
|
200
240
|
${serialize(elementInstance)}
|
|
201
241
|
</${elementTagName}>
|
|
202
242
|
`
|
|
203
|
-
|
|
243
|
+
: serialize(elementInstance);
|
|
204
244
|
} else {
|
|
205
245
|
console.warn('WARNING: No custom element class found for this entry point.');
|
|
206
246
|
}
|
|
207
247
|
|
|
208
248
|
return {
|
|
209
249
|
html,
|
|
210
|
-
metadata: definitions
|
|
250
|
+
metadata: definitions,
|
|
211
251
|
};
|
|
212
252
|
}
|
|
213
253
|
|
|
@@ -223,11 +263,8 @@ async function renderFromHTML(html, elements = []) {
|
|
|
223
263
|
|
|
224
264
|
return {
|
|
225
265
|
html: serialize(finalTree),
|
|
226
|
-
metadata: definitions
|
|
266
|
+
metadata: definitions,
|
|
227
267
|
};
|
|
228
268
|
}
|
|
229
269
|
|
|
230
|
-
export {
|
|
231
|
-
renderToString,
|
|
232
|
-
renderFromHTML
|
|
233
|
-
};
|
|
270
|
+
export { renderToString, renderFromHTML };
|