Skip to content

Commit 415a6d1

Browse files
authored
New HTML Parser (#283)
Deprecations: - Removes strict mode. New features: - HTML Comments support - Extremely fast - Lots of bug fixes - Smaller footprint - Fully supports any HTML parser
1 parent fd89b31 commit 415a6d1

File tree

28 files changed

+1188
-1633
lines changed

28 files changed

+1188
-1633
lines changed

packages/babel-plugin-transform-diffhtml/lib/index.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ export default function({ types: t }) {
135135
const visitor = {
136136
TaggedTemplateExpression(path, plugin) {
137137
let tagName = '';
138-
let strict = false;
139138

140139
if (path.node.tag.type === 'Identifier') {
141140
tagName = path.node.tag.name
@@ -152,10 +151,6 @@ export default function({ types: t }) {
152151
return;
153152
}
154153

155-
if (tagName === `${plugin.opts.tagName || 'html'}.strict`) {
156-
strict = true;
157-
}
158-
159154
const supplemental = {
160155
attributes: {},
161156
children: {},
@@ -242,7 +237,7 @@ export default function({ types: t }) {
242237
// serializing later. Using WASM the objects returned have getters which
243238
// are lost to the JSON.stringify call. By using createTree the values
244239
// are plucked and applied to the VTree object.
245-
const root = createTree(Internals.parse(HTML, null, { strict })).childNodes;
240+
const root = createTree(Internals.parse(HTML)).childNodes;
246241
const strRoot = JSON.stringify(root.length === 1 ? root[0] : root);
247242
const vTree = babylon.parse('(' + strRoot + ')');
248243

packages/diffhtml-components/lib/render-component.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export default function renderComponent(vTree, transaction) {
109109
render(props, state) {
110110
// Always render the latest `rawNodeName` of a VTree in case of
111111
// hot-reloading the cached value above wouldn't be correct.
112-
return createTree(vTree.rawNodeName(props, state));
112+
return createTree(RawComponent(props, state));
113113
}
114114

115115
/** @type {VTree | null} */

packages/diffhtml-components/test/component.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,12 @@ describe('Component', function() {
244244
strictEqual(componentVTree.rawNodeName, TestComponent);
245245
});
246246

247-
it('will associate the whitespace from the start of a fragment', () => {
247+
it('will associate the first element from the start of a fragment', () => {
248248
class TestComponent extends Component {
249249
render() {
250250
return html`
251251
<div />
252+
<p />
252253
`;
253254
}
254255
}
@@ -257,8 +258,6 @@ describe('Component', function() {
257258
innerHTML(this.fixture, TestComponent);
258259

259260
const componentVTree = ComponentTreeCache.get(this.fixture.childNodes[0]);
260-
// FIXME This should be a comment to make it more portable. Although text
261-
// will work for now.
262261
strictEqual(this.fixture.childNodes[0].nodeName, '#text');
263262
strictEqual(componentVTree.rawNodeName, TestComponent);
264263
});
@@ -617,7 +616,7 @@ describe('Component', function() {
617616

618617
const instance = this.fixture.querySelector('custom-component');
619618

620-
strictEqual(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
619+
strictEqual(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
621620
strictEqual(this.fixture.innerHTML, '<div><custom-component></custom-component></div>');
622621
});
623622

@@ -640,7 +639,7 @@ describe('Component', function() {
640639

641640
const instance = this.fixture.querySelector('custom-component');
642641

643-
strictEqual(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
642+
strictEqual(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
644643
strictEqual(this.fixture.innerHTML, '<custom-component></custom-component>');
645644
});
646645

packages/diffhtml-components/test/integration/web-component.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Web Component', function() {
5353

5454
const instance = this.fixture.querySelector('custom-component');
5555

56-
equal(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
56+
equal(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
5757
equal(this.fixture.innerHTML, '<custom-component></custom-component>');
5858
});
5959

@@ -71,7 +71,7 @@ describe('Web Component', function() {
7171

7272
const instance = this.fixture.querySelector('custom-component');
7373

74-
equal(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
74+
equal(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
7575
equal(this.fixture.innerHTML, '<div><custom-component></custom-component></div>');
7676
});
7777

@@ -91,7 +91,7 @@ describe('Web Component', function() {
9191

9292
const instance = this.fixture.querySelector('custom-component');
9393

94-
equal(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
94+
equal(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
9595
equal(this.fixture.innerHTML, '<custom-component></custom-component>');
9696
});
9797

@@ -217,7 +217,7 @@ describe('Web Component', function() {
217217

218218
const instance = this.fixture.querySelector('custom-component');
219219

220-
equal(instance.shadowRoot.childNodes[1].outerHTML, '<div>Hello world</div>');
220+
equal(instance.shadowRoot.childNodes[0].outerHTML, '<div>Hello world</div>');
221221
equal(this.fixture.innerHTML, '<div><custom-component></custom-component></div>');
222222
});
223223

@@ -301,7 +301,7 @@ describe('Web Component', function() {
301301
const inner = this.fixture.querySelector('inner-component');
302302

303303
equal(
304-
instance.shadowRoot.childNodes[1].outerHTML,
304+
instance.shadowRoot.childNodes[0].outerHTML,
305305
'<div><slot></slot></div>',
306306
);
307307

packages/diffhtml-middleware-linter/lib/index.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
const { assign, keys } = Object;
22
const { isArray } = Array;
33

4+
/**
5+
* @see https://htmlhint.com/docs/user-guide/list-rules
6+
*/
47
const defaults = {
5-
"tagname-lowercase": true,
8+
// Doctype and Head
9+
"doctype-first": true, // requires parser change
10+
"doctype-html5": true, // requires parser change
11+
"html-lang-require": true, // requires parser change
12+
"head-script-disabled": true,
13+
"style-disabled": false,
14+
"script-disabled": false,
15+
"title-require": true,
16+
17+
// Attributes
618
"attr-lowercase": true,
19+
"attr-no-duplication": true, // requires parser change
20+
"attr-no-unnecessary-whitespace": true, // requires parser change
721
"attr-value-double-quotes": true, // requires parser change
822
"attr-value-not-empty": false,
9-
"attr-no-duplication": true, // requires parser change
10-
"doctype-first": true, // requires parser change
1123
"tag-pair": true, // requires parser change
1224
"empty-tag-not-self-closed": true, // requires parser change
1325
"spec-char-escape": true, // requires parser change
26+
"tagname-lowercase": true,
1427
"id-unique": true,
1528
"src-not-empty": true,
16-
"title-require": true,
1729
"alt-require": true,
18-
"doctype-html5": true, // requires parser change
1930
"id-class-value": "dash",
20-
"style-disabled": false,
2131
"inline-style-disabled": false,
2232
"inline-script-disabled": false,
2333
"space-tab-mixed-disabled": "space", // requires parser change
2434
"id-class-ad-disabled": false, // tbd
2535
"href-abs-or-rel": false, // tbd
2636
"attr-unsafe-chars": true,
27-
"head-script-disabled": true,
2837
};
2938

3039
const unsafeRegexp = /[\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;

packages/diffhtml-static-sync/sync.js

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ function open() {
8282
) {
8383
const children = html(markup);
8484

85+
console.log(children, markup);
86+
8587
if (children.childNodes.length > 1) {
8688
outerHTML(document.documentElement, children.childNodes[1]);
8789
}

packages/diffhtml-website/components/layout.js

+20-18
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,29 @@ module.exports = ({ path, page, pages, content }) => html`
3636
</layer>
3737
3838
<layer id="main">
39-
<div class="open-menu"></div>
40-
<header>
41-
<h1>
42-
<a href="/"><img width="120" height="51" src="./images/diffhtml-logo-fit.png"></a>
43-
<div>
44-
<p class="name">diffHTML</p><sub>v${version}</sub>
45-
<p>An easy-to-use Virtual DOM built for the web!</p>
46-
</div>
47-
</h1>
48-
</header>
39+
<div class="page-content">
40+
<div class="open-menu"></div>
41+
<header>
42+
<h1>
43+
<a href="/"><img width="120" height="51" src="./images/diffhtml-logo-fit.png"></a>
44+
<div>
45+
<p class="name">diffHTML</p><sub>v${version}</sub>
46+
<p>An easy-to-use Virtual DOM built for the web!</p>
47+
</div>
48+
</h1>
49+
</header>
4950
50-
<hr />
51+
<hr />
5152
52-
<section id="content">${content}</section>
53+
<section id="content">${content}</section>
5354
54-
<a
55-
href=${`https://github.com/tbranyen/diffhtml/edit/master/packages/diffhtml-website/pages/${path.replace('.html', '.md')}`}
56-
id="edit-on-github"
57-
>
58-
Edit on GitHub &nbsp; <span class="fa fa-github"></span>
59-
</a>
55+
<a
56+
href=${`https://github.com/tbranyen/diffhtml/edit/master/packages/diffhtml-website/pages/${path.replace('.html', '.md')}`}
57+
id="edit-on-github"
58+
>
59+
Edit on GitHub &nbsp; <span class="fa fa-github"></span>
60+
</a>
61+
</div>
6062
6163
<footer>
6264
<a target="_blank" href="https://twitter.com/tbranyen" style="text-decoration: none;">

packages/diffhtml-website/config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
"Parser": ["parser.html", {
2424
"Options": "#options",
25-
"Dynamic values": "#dynamic-values"
25+
"Dynamic values": "#dynamic-values",
26+
"Rust WASM": "#rust-wasm"
2627
}],
2728

2829
"Middleware": ["middleware.html", {

packages/diffhtml-website/pages/api.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Core API
22

3+
The
4+
35
This documentation covers the core public API. All methods and internals work
46
in the browser and directly in [Node.js](https://nodejs.org/en/) with or without
57
[jsdom](https://github.com/jsdom/jsdom).
68

79
**Terminology:**
810

9-
- **VTree**: You will see them mentioned throughout the documentation. They are
11+
- **VTree**: The internal VDOM structure. They are
1012
JavaScript objects that represent a DOM node. They store information such as
1113
the tagName, what the childNodes are, and more. A reference to a VTree can get
1214
you access to the DOM node it represents. They are used throughout the
@@ -417,7 +419,7 @@ passing a config object to `innerHTML`, `outerHTML`, and `toString`.
417419

418420
In the case of query string and environment variables, uppercase the variables
419421
and prefix with `DIFF_`. So `inner` becomes `DIFF_INNER`. For `parser` use a
420-
JSON string: `JSON.stringify({ parser: { strict: true } })`.
422+
JSON string: `JSON.stringify({ parser: { rawElements: ['div'] } })`.
421423

422424
- [`inner`](#options-inner)
423425
- [`tasks`](#options-tasks)
@@ -534,8 +536,8 @@ innerHTML(document.body, `Some value`, {
534536

535537
### <a href="#options-parser">parser `Object`</a>
536538

537-
These options modify the parser by making it more strict or changing which
538-
elements are treated as block or self closing.
539+
These options modify the parser by changing which elements are treated as
540+
block or self closing.
539541

540542
[Learn more about these options](/parser.html#options)
541543

@@ -548,7 +550,7 @@ import { innerHTML } from 'diffhtml';
548550

549551
innerHTML(document.body, `
550552
<h1>Hello world</h2>
551-
`, { parser: { strict: true } });
553+
`, { parser: { rawElements: ['div'] } });
552554
```
553555

554556
---

packages/diffhtml-website/pages/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ approachable to new programmers, intermediates, and professionals.
1313
- ESM/CJS/UMD + Minified ES5 builds
1414
- Middleware
1515
- Efficient Virtual DOM
16-
- Object pooling to optimize GC
16+
- Object pooling and custom HTML parser to optimize GC
1717
- Strict mode TypeScript via checkJS
1818

1919
<a name="getting-started"></a>

packages/diffhtml-website/pages/parser.md

+18-57
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
One of the best features of diffHTML is the parser. This drives the compiling
44
of declarative markup and supports a variety of syntaxes. You can make compound
5-
documents that are comprised of HTML, SVG, or XML. The result is a JSX-like
6-
object which is used by the core engine and the Babel transform.
5+
documents that are comprised of HTML, SVG, or XML. The result is a VTree which
6+
makes it equivalent to the `createTree` function.
77

8-
When you use `innerHTML`, `outerHTML`, or `html` and pass markup, it will run
9-
through this very fast and efficient parser. Usually production code does not
10-
run the parser. You will pre-compile your markup using the [Babel
11-
transform](/tools.html#babel-transform).
8+
Usually production code does not run the parser. You will pre-compile your markup
9+
using the [Babel transform](/tools.html#babel-transform) that also conveniently
10+
uses the same parser to ensure parity.
1211

13-
The parser can read full HTML documents including doctype, html/head/body/title
14-
etc tags, unwrapped fragments, and more!
12+
The built in parser can read full HTML documents including comments, doctype,
13+
html/head/body/title page tags, multiple unwrapped top-level root elements,
14+
and has support for optional tags. It even supports nested markup within tags,
15+
such as <code>srcdoc="<some markup />"</code> in <code>&lt;iframe/&gt;</code>.
16+
17+
While the parser works for most use cases out-of-the-box, you may want to use
18+
something else. The parser is fully overrieable and allows for any string-based
19+
input, so long as it can be compiled to a tree structure.
1520

1621
**Using with innerHTML:**
1722

@@ -113,56 +118,12 @@ console.log(Internals.parse(`
113118

114119
## <a href="#options">Options</a>
115120

116-
The parser is somewhat configurable, allowing you to opt into a strict-mode for
117-
erroring if invalid markup is passed. This could be useful to pair with the
118-
[HTML Linter Middleware](/middleware.html#html-linter).
119-
120-
- [`strict`](#strict-mode) - Toggle strict mode parsing
121-
- [`rawElements`](#block-elements) - Modify the list of elements that have raw values
122-
- [`selfClosingElements`](#self-closing) - Modify the list of elements that can self close
123-
124-
<a name="strict-mode"></a>
125-
126-
---
127-
128-
### <a href="#strict-mode">Strict mode</a>
129-
130-
By default the parser operates in loose-mode which is forgiving of missing
131-
closing tags, poor markup, and other common issues. When opting into strict
132-
mode you will receive errors if you don't properly self close tags, have
133-
mismatched tag names, etc.
134-
135-
```js
136-
import { innerHTML } from 'diffhtml';
137-
138-
const options = {
139-
parser: {
140-
strict: true,
141-
}
142-
};
121+
The parser is somewhat configurable, allowing you to change the list of self
122+
closing items. This could be useful to pair with the [HTML Linter
123+
Middleware](/middleware.html#html-linter).
143124

144-
// Will be fine since the elements match
145-
innerHTML(document.body, `
146-
<h1>Hello world</h1>
147-
`, options);
148-
149-
// Will throw since the elements do not match
150-
innerHTML(document.body, `
151-
<h1>Hello world</h2>
152-
`, options);
153-
```
154-
155-
Unlike the other two options below, this feature can be configured using the
156-
tagged template [`html`](/api.html#html) directly.
157-
158-
```js
159-
import { html } from 'diffhtml';
160-
161-
// Will throw an error due to the tag mismatch
162-
html.strict`
163-
<p></div>
164-
`;
165-
```
125+
- [`rawElements`](#block-elements) - Modify the list of elements that have text values instead of markup
126+
- [`voidElements`](#self-closing) - Modify the list of elements that can self close
166127

167128
<a name="block-elements"></a>
168129

0 commit comments

Comments
 (0)