Skip to content
This repository has been archived by the owner on Oct 25, 2024. It is now read-only.

Updating CSS and HTML parser. #120

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16
- name: install, build, test
run: |
yarn --frozen-lockfile
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"text"
],
"moduleNameMapper": {
"^critters$": "<rootDir>/packages/critters/src/index.js"
"^critters$": "<rootDir>/packages/critters/src/index.js",
"#(.*)": "<rootDir>/node_modules/$1"
},
"collectCoverageFrom": [
"packages/*/src/**/*.js"
Expand All @@ -51,6 +52,8 @@
"watchPathIgnorePatterns": [
"node_modules",
"dist"
],
"transformIgnorePatterns": [
]
},
"prettier": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`External CSS should match snapshot 1`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>External CSS Demo</title>
<style>:root {
font-size: 10px;
Expand Down Expand Up @@ -64,9 +66,9 @@ footer {
<h1>My first styled page</h1>
<p>Welcome to my styled page!</p>
<footer>Made 5 April 2004</footer>
<script src=\\"bundle.js\\"></script>

<link rel=\\"stylesheet\\" href=\\"main.css\\"></body></html>"
<script src=\\"bundle.js\\"></script><link rel=\\"stylesheet\\" href=\\"main.css\\"></body>
</html>
"
`;

exports[`External CSS should prune external sheet 1`] = `
Expand All @@ -87,7 +89,9 @@ exports[`Inline <style> pruning should remove entire unused stylesheets 1`] = `
`;

exports[`Inline <style> pruning should remove unused rules 1`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>Basic Demo</title>
<style>
body {
Expand Down Expand Up @@ -156,13 +160,15 @@ exports[`Inline <style> pruning should remove unused rules 1`] = `
<h1>My first styled page</h1>
<p>Welcome to my styled page!</p>
<footer>Made 5 April 2004</footer>
<script src=\\"bundle.js\\"></script>

</body></html>"
<script src=\\"bundle.js\\"></script></body>
</html>
"
`;

exports[`options { additionalStylesheets:["*.css"] } should match snapshot 1`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>Additional Stylesheet CSS Demo</title>
<style>html {
padding: 0px;
Expand All @@ -189,13 +195,15 @@ html {
<h1 class=\\"additional-style\\">My first styled page</h1>
<p>Welcome to my styled page!</p>
<footer>Made 5 April 2004</footer>
<script src=\\"bundle.js\\"></script>

<link rel=\\"stylesheet\\" href=\\"main.css\\"></body></html>"
<script src=\\"bundle.js\\"></script><link rel=\\"stylesheet\\" href=\\"main.css\\"></body>
</html>
"
`;

exports[`options { async:true } should match snapshot 1`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>External CSS Demo</title>
<style>:root {
font-size: 10px;
Expand Down Expand Up @@ -258,13 +266,15 @@ footer {
<h1>My first styled page</h1>
<p>Welcome to my styled page!</p>
<footer>Made 5 April 2004</footer>
<script src=\\"bundle.js\\"></script>

<link rel=\\"stylesheet\\" href=\\"main.css\\"></body></html>"
<script src=\\"bundle.js\\"></script><link rel=\\"stylesheet\\" href=\\"main.css\\"></body>
</html>
"
`;

exports[`publicPath should match snapshot 1`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>External CSS Demo</title>
<style>:root {
font-size: 10px;
Expand Down Expand Up @@ -327,9 +337,9 @@ footer {
<h1>My first styled page</h1>
<p>Welcome to my styled page!</p>
<footer>Made 5 April 2004</footer>
<script src=\\"/_public/bundle.js\\"></script>

<link rel=\\"stylesheet\\" href=\\"/_public/main.css\\"></body></html>"
<script src=\\"/_public/bundle.js\\"></script><link rel=\\"stylesheet\\" href=\\"/_public/main.css\\"></body>
</html>
"
`;

exports[`publicPath should prune external sheet 1`] = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ exports[`Usage without html-webpack-plugin should process the first html asset 1
`;

exports[`Usage without html-webpack-plugin should process the first html asset 2`] = `
"<!DOCTYPE html><html><head>
"<!DOCTYPE html>
<html>
<head>
<title>Basic Demo</title>

<style id=\\"used\\">
Expand All @@ -20,9 +22,9 @@ exports[`Usage without html-webpack-plugin should process the first html asset 2
</head>
<body>
<h1>Some HTML Here</h1>


</body></html>"
</body>
</html>
"
`;

exports[`webpack compilation 1`] = `
Expand Down
4 changes: 2 additions & 2 deletions packages/critters-webpack-plugin/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('options', () => {
let output;
beforeAll(async () => {
output = await compileToHtml('additionalStylesheets', configure, {
additionalStylesheets: ['*.css'],
additionalStylesheets: ['*.css']
});
});

Expand Down Expand Up @@ -177,7 +177,7 @@ describe('options', () => {
const link = output.document.querySelector('link[rel="stylesheet"]');
expect(link).not.toBeNull();
expect(link).toHaveProperty('href', 'main.css');
expect(output.document.body.lastChild).toBe(link);
expect(output.document.body.lastElementChild).toBe(link);
});

it('should match snapshot', () => {
Expand Down
9 changes: 5 additions & 4 deletions packages/critters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@
"prepare": "npm run -s build"
},
"dependencies": {
"chalk": "^4.1.0",
"css-select": "^4.2.0",
"chalk": "^5.2.0",
"css-select": "^5.1.0",
"htmlparser2": "^8.0.2",
janicklas-ralph marked this conversation as resolved.
Show resolved Hide resolved
"parse5": "^6.0.1",
"parse5-htmlparser2-tree-adapter": "^6.0.1",
Copy link
Contributor

@alan-agius4 alan-agius4 May 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed

Suggested change
"parse5": "^6.0.1",
"parse5-htmlparser2-tree-adapter": "^6.0.1",

"postcss": "^8.3.7",
"pretty-bytes": "^5.3.0"
"postcss": "^8.4.23",
"pretty-bytes": "^6.1.0"
},
"devDependencies": {
"documentation": "^13.0.2",
Expand Down
76 changes: 17 additions & 59 deletions packages/critters/src/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,23 @@
* the License.
*/

import parse5 from 'parse5';
import { selectAll, selectOne } from 'css-select';
import treeAdapter from 'parse5-htmlparser2-tree-adapter';

// htmlparser2 has a relatively DOM-like tree format, which we'll massage into a DOM elsewhere
const PARSE5_OPTS = {
treeAdapter
};
import { parseDocument, DomUtils } from 'htmlparser2';
import { Element, Text } from 'domhandler';
import render from 'dom-serializer';

/**
* Parse HTML into a mutable, serializable DOM Document.
* The DOM implementation is an htmlparser2 DOM enhanced with basic DOM mutation methods.
* @param {String} html HTML to parse into a Document instance
*/
export function createDocument(html) {
const document = /** @type {HTMLDocument} */ (
parse5.parse(html, PARSE5_OPTS)
);
const document = /** @type {HTMLDocument} */ (parseDocument(html));

defineProperties(document, DocumentExtensions);

// Extend Element.prototype with DOM manipulation methods.
const scratch = document.createElement('div');
// Get a reference to the base Node class - used by createTextNode()
document.$$Node = scratch.constructor;
const elementProto = Object.getPrototypeOf(scratch);
defineProperties(elementProto, ElementExtensions);
elementProto.ownerDocument = document;
defineProperties(Element.prototype, ElementExtensions);

return document;
}
Expand All @@ -51,7 +40,7 @@ export function createDocument(html) {
* @param {HTMLDocument} document A Document, such as one created via `createDocument()`
*/
export function serializeDocument(document) {
return parse5.serialize(document, PARSE5_OPTS);
return render(document);
}

/** @typedef {treeAdapter.Document & typeof ElementExtensions} HTMLDocument */
Expand All @@ -75,31 +64,31 @@ const ElementExtensions = {

insertBefore(child, referenceNode) {
if (!referenceNode) return this.appendChild(child);
treeAdapter.insertBefore(this, child, referenceNode);
DomUtils.prepend(referenceNode, child);
return child;
},

appendChild(child) {
treeAdapter.appendChild(this, child);
DomUtils.appendChild(this, child);
return child;
},

removeChild(child) {
treeAdapter.detachNode(child);
DomUtils.removeElement(child);
},

remove() {
treeAdapter.detachNode(this);
DomUtils.removeElement(this);
},

textContent: {
get() {
return getText(this);
return DomUtils.getText(this);
},

set(text) {
this.children = [];
treeAdapter.insertText(this, text);
DomUtils.appendChild(this, new Text(text));
}
},

Expand Down Expand Up @@ -159,23 +148,12 @@ const DocumentExtensions = {
documentElement: {
get() {
// Find the first <html> element within the document
return this.childNodes.filter(
return this.filter(
(child) => String(child.tagName).toLowerCase() === 'html'
);
}
},

compatMode: {
get() {
const compatMode = {
'no-quirks': 'CSS1Compat',
quirks: 'BackCompat',
'limited-quirks': 'CSS1Compat'
};
return compatMode[treeAdapter.getDocumentMode(this)];
}
},

head: {
get() {
return this.querySelector('head');
Expand All @@ -189,30 +167,23 @@ const DocumentExtensions = {
},

createElement(name) {
return treeAdapter.createElement(name, null, []);
return new Element(name);
},

createTextNode(text) {
// there is no dedicated createTextNode equivalent exposed in htmlparser2's DOM
const Node = this.$$Node;
return new Node({
type: 'text',
data: text,
parent: null,
prev: null,
next: null
});
return new Text(text);
},

querySelector(sel) {
return selectOne(sel, this.documentElement);
return selectOne(sel, this);
},

querySelectorAll(sel) {
if (sel === ':root') {
return this;
}
return selectAll(sel, this.documentElement);
return selectAll(sel, this);
}
};

Expand Down Expand Up @@ -245,16 +216,3 @@ function reflectedProperty(attributeName) {
}
};
}

/**
* Helper to get the text content of a node
* https://github.com/fb55/domutils/blob/master/src/stringify.ts#L21
* @private
*/
function getText(node) {
if (Array.isArray(node)) return node.map(getText).join('');
if (treeAdapter.isElementNode(node))
return node.name === 'br' ? '\n' : getText(node.children);
if (treeAdapter.isTextNode(node)) return node.data;
return '';
}
20 changes: 17 additions & 3 deletions packages/critters/test/__snapshots__/critters.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Critters Basic Usage 1`] = `
"<html><head>
"<html>
<head>
<style>h1{color:blue}p{color:purple}</style><link rel=\\"preload\\" href=\\"/style.css\\" as=\\"style\\">
</head>
<body>
<h1>Hello World!</h1>
<p>This is a paragraph</p>

<link rel=\\"stylesheet\\" href=\\"/style.css\\"></body></html>"
<link rel=\\"stylesheet\\" href=\\"/style.css\\"></body>
</html>"
`;

exports[`Critters Run on HTML file 1`] = `
"<html>
<head>
<style>h1{color:blue}p{color:purple}</style><link rel=\\"preload\\" href=\\"styles.css\\" as=\\"style\\">
</head>
<body>
<h1>Hello World!</h1>
<p>This is a paragraph</p>
<link rel=\\"stylesheet\\" href=\\"styles.css\\"></body>
</html>
"
`;
Loading