Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d9fb67b

Browse files
committedMay 2, 2021
Use ESM
1 parent 70277fb commit d9fb67b

File tree

33 files changed

+418
-669
lines changed

33 files changed

+418
-669
lines changed
 

‎.babelrc

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
*.d.ts
33
*.log
44
coverage/
5-
dist/
65
node_modules/
76
yarn.lock

‎.prettierignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
coverage/
2-
dist/
3-
src/__fixtures__/**/*.html
2+
*.html
43
*.md

‎index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {toDom} from './lib/index.js'

‎karma.conf.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

‎src/index.js renamed to ‎lib/index.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import ns from 'web-namespaces'
2-
import find from 'property-information/find.js'
3-
import html from 'property-information/html.js'
4-
import svg from 'property-information/svg.js'
1+
import {webNamespaces} from 'web-namespaces'
2+
import {find, html, svg} from 'property-information'
53

64
/* eslint-env browser */
75

@@ -40,7 +38,7 @@ function root(node, options) {
4038

4139
// Take namespace of the first child.
4240
if (typeof optionsNamespace === 'undefined') {
43-
namespace = properties.xmlns || ns.html
41+
namespace = properties.xmlns || webNamespaces.html
4442
}
4543
}
4644
}
@@ -89,21 +87,21 @@ function element(node, options) {
8987
const {namespace, doc} = options
9088
let impliedNamespace = options.impliedNamespace || namespace
9189
const {
92-
tagName = impliedNamespace === ns.svg ? 'g' : 'div',
90+
tagName = impliedNamespace === webNamespaces.svg ? 'g' : 'div',
9391
properties = {},
9492
children = []
9593
} = node
9694

9795
if (
9896
(impliedNamespace === null ||
9997
impliedNamespace === undefined ||
100-
impliedNamespace === ns.html) &&
98+
impliedNamespace === webNamespaces.html) &&
10199
tagName === 'svg'
102100
) {
103-
impliedNamespace = ns.svg
101+
impliedNamespace = webNamespaces.svg
104102
}
105103

106-
const schema = impliedNamespace === ns.svg ? svg : html
104+
const schema = impliedNamespace === webNamespaces.svg ? svg : html
107105

108106
const result =
109107
impliedNamespace === null || impliedNamespace === undefined
@@ -172,6 +170,6 @@ function appendAll(node, children, options) {
172170
return node
173171
}
174172

175-
export default function toDOM(hast, options = {}) {
173+
export function toDom(hast, options = {}) {
176174
return transform(hast, {...options, doc: options.document || document})
177175
}

‎package.json

Lines changed: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,52 +24,34 @@
2424
"Keith McKnight <keith@mcknig.ht> (https://keith.mcknig.ht)",
2525
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
2626
],
27-
"main": "dist/hast-util-to-dom.js",
28-
"module": "dist/hast-util-to-dom.mjs",
27+
"sideEffects": false,
28+
"type": "module",
29+
"main": "index.js",
2930
"files": [
30-
"dist/"
31+
"lib/",
32+
"index.js"
3133
],
3234
"dependencies": {
33-
"property-information": "^5.1.0",
34-
"web-namespaces": "^1.1.3"
35+
"property-information": "^6.0.0",
36+
"web-namespaces": "^2.0.0"
3537
},
3638
"devDependencies": {
37-
"@babel/core": "^7.0.0",
38-
"@babel/plugin-external-helpers": "^7.0.0",
39-
"@babel/preset-env": "^7.0.0",
40-
"@rollup/plugin-babel": "^5.0.0",
41-
"@rollup/plugin-commonjs": "^18.0.0",
42-
"@rollup/plugin-json": "^4.0.0",
43-
"@rollup/plugin-node-resolve": "^11.0.0",
44-
"babel-jest": "^26.0.0",
39+
"c8": "^7.0.0",
4540
"glob": "^7.0.0",
46-
"hastscript": "^6.0.0",
47-
"jasmine-core": "^3.0.0",
48-
"jest-cli": "^26.0.0",
49-
"karma": "^6.0.0",
50-
"karma-chrome-launcher": "^3.0.0",
51-
"karma-firefox-launcher": "^2.0.0",
52-
"karma-jasmine": "^4.0.0",
53-
"karma-mocha-reporter": "^2.0.0",
54-
"karma-rollup-preprocessor": "^7.0.0",
55-
"karma-safari-launcher": "^1.0.0",
41+
"hastscript": "^7.0.0",
42+
"jsdom": "^16.5.3",
5643
"prettier": "^2.0.0",
5744
"remark-cli": "^9.0.0",
5845
"remark-preset-wooorm": "^8.0.0",
59-
"rollup": "^2.0.0",
46+
"tape": "^5.0.0",
6047
"w3c-xmlserializer": "^2.0.0",
61-
"xo": "^0.38.0"
48+
"xo": "^0.39.0"
6249
},
6350
"scripts": {
64-
"build": "rollup -c",
6551
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
66-
"test:api": "jest",
67-
"test:karma": "karma start --single-run --browsers ChromeHeadless,FirefoxHeadless,Safari",
68-
"test:karma:chrome": "karma start --single-run --browsers ChromeHeadless",
69-
"test:karma:firefox": "karma start --single-run --browsers FirefoxHeadless",
70-
"test:karma:safari": "karma start --single-run --browsers Safari",
71-
"test:dev": "jest --watchAll",
72-
"test": "npm run build && npm run format && npm run test:api"
52+
"test-api": "node test/index.js",
53+
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js",
54+
"test": "npm run format && npm run test-coverage"
7355
},
7456
"prettier": {
7557
"tabWidth": 2,
@@ -82,20 +64,6 @@
8264
"xo": {
8365
"prettier": true
8466
},
85-
"jest": {
86-
"collectCoverage": true,
87-
"coveragePathIgnorePatterns": [
88-
"/src/utils.js"
89-
],
90-
"coverageThreshold": {
91-
"global": {
92-
"branches": 100,
93-
"functions": 100,
94-
"lines": 100,
95-
"statements": 100
96-
}
97-
}
98-
},
9967
"remarkConfig": {
10068
"plugins": [
10169
"preset-wooorm"

‎readme.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212

1313
## Install
1414

15-
[yarn][]:
16-
17-
```sh
18-
yarn add hast-util-to-dom
19-
```
15+
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c):
16+
Node 12+ is needed to use it and it must be `import`ed instead of `require`d.
2017

2118
[npm][]:
2219

@@ -70,6 +67,9 @@ Buddy, that’s the web!
7067

7168
## API
7269

70+
This package exports the following identifiers: `toDom`.
71+
There is no default export.
72+
7373
### `toDom(node[, options])`
7474

7575
Transform a [**hast**][hast] [*tree*][tree] to a DOM tree.
@@ -145,8 +145,6 @@ abide by its terms.
145145

146146
[chat]: https://github.com/syntax-tree/unist/discussions
147147

148-
[yarn]: https://yarnpkg.com/lang/en/docs/install
149-
150148
[npm]: https://docs.npmjs.com/cli/install
151149

152150
[license]: license

‎rollup.config.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

‎src/fixtures.test.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

‎src/index.test.js

Lines changed: 0 additions & 490 deletions
This file was deleted.

‎src/utils.js

Lines changed: 0 additions & 14 deletions
This file was deleted.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

‎test/index.js

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import test from 'tape'
4+
import glob from 'glob'
5+
import {JSDOM} from 'jsdom'
6+
import {webNamespaces} from 'web-namespaces'
7+
import {h, s} from 'hastscript'
8+
import serialize from 'w3c-xmlserializer'
9+
import {toDom} from '../index.js'
10+
11+
globalThis.document = new JSDOM().window.document
12+
13+
test('hast-util-to-dom', (t) => {
14+
t.equal(
15+
serializeNodeToHtmlString(toDom({type: 'root'})),
16+
'',
17+
'creates an empty root node'
18+
)
19+
20+
t.equal(
21+
serializeNodeToHtmlString(
22+
toDom({
23+
type: 'root',
24+
children: [
25+
{type: 'element', tagName: 'html', properties: {}, children: []}
26+
]
27+
})
28+
),
29+
'<html></html>',
30+
'creates a root node with a document element'
31+
)
32+
33+
t.equal(
34+
serializeNodeToHtmlString(
35+
toDom({
36+
type: 'root',
37+
children: [
38+
{type: 'doctype', name: 'html', public: null, system: null},
39+
{
40+
type: 'element',
41+
tagName: 'html',
42+
properties: {},
43+
children: [
44+
{type: 'element', tagName: 'head', properties: {}, children: []},
45+
{type: 'element', tagName: 'body', properties: {}, children: []}
46+
]
47+
}
48+
]
49+
})
50+
),
51+
'<!DOCTYPE html><html><head></head><body></body></html>',
52+
'creates a root node with a doctype'
53+
)
54+
55+
t.equal(
56+
serializeNodeToHtmlString(toDom({type: 'text', value: 'hello world'})),
57+
'hello world',
58+
'creates a text node'
59+
)
60+
61+
t.equal(
62+
serializeNodeToHtmlString(toDom(h('div'))),
63+
'<div></div>',
64+
'creates an element node'
65+
)
66+
67+
t.equal(
68+
serializeNodeToHtmlString(toDom({type: 'something-else'})),
69+
'<div></div>',
70+
'creates an unknown node in HTML'
71+
)
72+
73+
t.equal(
74+
serializeNodeToHtmlString(
75+
toDom({type: 'something-else'}, {namespace: webNamespaces.svg})
76+
),
77+
'<g/>',
78+
'creates an unknown node in SVG'
79+
)
80+
81+
t.equal(
82+
serializeNodeToHtmlString(
83+
toDom({
84+
type: 'something-else',
85+
children: [{type: 'text', value: 'value'}]
86+
})
87+
),
88+
'<div>value</div>',
89+
'creates an unknown node (with children)'
90+
)
91+
92+
t.equal(
93+
serializeNodeToHtmlString(toDom(h('span', ['hello', 'world']))),
94+
'<span>helloworld</span>',
95+
'creates text nodes inside an element node'
96+
)
97+
98+
t.equal(
99+
serializeNodeToHtmlString(toDom(h('#foo.bar', 'text'))),
100+
'<div id="foo" class="bar">text</div>',
101+
'creates an html element'
102+
)
103+
104+
t.equal(
105+
serializeNodeToHtmlString(
106+
toDom(s('#foo.bar', s('circle')), {namespace: webNamespaces.svg})
107+
),
108+
'<g id="foo" class="bar"><circle/></g>',
109+
'creates SVG elements'
110+
)
111+
112+
t.equal(
113+
serializeNodeToHtmlString(
114+
toDom(h('input', {disabled: true, value: 'foo'}))
115+
),
116+
'<input disabled="" value="foo" />',
117+
'creates an input node with some attributes'
118+
)
119+
120+
t.equal(
121+
serializeNodeToHtmlString(
122+
toDom(h('input', {type: 'checkbox', checked: true}))
123+
),
124+
'<input type="checkbox" checked="" />',
125+
'creates an checkbox where `checked` must be set as a property'
126+
)
127+
128+
t.equal(
129+
serializeNodeToHtmlString(
130+
toDom({
131+
type: 'element',
132+
tagName: 'div',
133+
properties: {allowFullScreen: false},
134+
children: []
135+
})
136+
),
137+
'<div></div>',
138+
'handles falsey booleans correctly'
139+
)
140+
141+
t.equal(
142+
serializeNodeToHtmlString(toDom(h('div', {class: ['foo', 'bar']}))),
143+
'<div class="foo bar"></div>',
144+
'handles space-separated attributes correctly'
145+
)
146+
147+
const img = 'data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA='
148+
t.equal(
149+
serializeNodeToHtmlString(
150+
toDom(h('img', {srcSet: [`${img} 1x`, `${img} 2x`]}))
151+
),
152+
`<img srcset="${img} 1x, ${img} 2x" />`,
153+
'handles comma-separated attributes correctly'
154+
)
155+
156+
t.equal(
157+
serializeNodeToHtmlString(
158+
toDom({
159+
type: 'doctype',
160+
name: 'html',
161+
public: null,
162+
system: 'http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd'
163+
})
164+
),
165+
'<!DOCTYPE html SYSTEM "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd">',
166+
'creates a doctype node'
167+
)
168+
169+
t.equal(
170+
serializeNodeToHtmlString(toDom({type: 'comment', value: 'after'})),
171+
'<!--after-->',
172+
'creates a comment'
173+
)
174+
175+
t.equal(
176+
serializeNodeToHtmlString(
177+
toDom(
178+
h('.alpha', [
179+
'bravo ',
180+
h('b', 'charlie'),
181+
' delta ',
182+
h('a.echo', {download: true}, 'foxtrot')
183+
])
184+
)
185+
),
186+
'<div class="alpha">bravo <b>charlie</b> delta <a class="echo" download="">foxtrot</a></div>',
187+
'creates nested nodes with attributes'
188+
)
189+
190+
t.equal(
191+
serializeNodeToHtmlString(
192+
toDom({
193+
type: 'root',
194+
children: [
195+
{
196+
type: 'element',
197+
tagName: 'title',
198+
properties: {},
199+
children: [{type: 'text', value: 'Hi'}]
200+
},
201+
{
202+
type: 'element',
203+
tagName: 'h2',
204+
properties: {},
205+
children: [{type: 'text', value: 'Hello world!'}]
206+
}
207+
]
208+
})
209+
),
210+
'<html><title>Hi</title><h2>Hello world!</h2></html>',
211+
'wraps a fragment in an HTML element'
212+
)
213+
214+
t.equal(
215+
serializeNodeToHtmlString(
216+
toDom(
217+
{
218+
type: 'root',
219+
children: [
220+
{
221+
type: 'element',
222+
tagName: 'title',
223+
properties: {},
224+
children: [{type: 'text', value: 'Hi'}]
225+
},
226+
{
227+
type: 'element',
228+
tagName: 'h2',
229+
properties: {},
230+
children: [{type: 'text', value: 'Hello world!'}]
231+
}
232+
]
233+
},
234+
{fragment: true}
235+
)
236+
),
237+
'<title>Hi</title><h2>Hello world!</h2>',
238+
'does not wrap a fragment when the option is specified'
239+
)
240+
241+
t.equal(
242+
serializeNodeToHtmlString(
243+
toDom(
244+
{type: 'root', children: [h('html')]},
245+
{namespace: 'http://example.com'}
246+
)
247+
),
248+
'<html xmlns="http://example.com"/>',
249+
'should support a given namespace'
250+
)
251+
252+
const doc = {
253+
createElementNS(namespace, tagName) {
254+
const name = tagName === 'h1' ? 'h2' : tagName
255+
return globalThis.document.createElementNS(namespace, name)
256+
},
257+
createTextNode(value) {
258+
return globalThis.document.createTextNode(value.toUpperCase())
259+
},
260+
implementation: {
261+
createDocument(namespace, qualifiedName, documentType) {
262+
return globalThis.document.implementation.createDocument(
263+
namespace,
264+
qualifiedName,
265+
documentType
266+
)
267+
}
268+
}
269+
}
270+
271+
t.equal(
272+
serializeNodeToHtmlString(
273+
toDom(
274+
{
275+
type: 'root',
276+
children: [h('html', [h('title', 'foo'), h('h1', 'bar')])]
277+
},
278+
{document: doc}
279+
)
280+
),
281+
'<html><title>FOO</title><h2>BAR</h2></html>',
282+
'should support a given document'
283+
)
284+
285+
t.equal(
286+
serializeNodeToHtmlString(toDom(h('div', {ariaChecked: true}))),
287+
'<div aria-checked="true"></div>',
288+
'handles booleanish attribute with `true` value correctly'
289+
)
290+
291+
t.equal(
292+
serializeNodeToHtmlString(toDom(h('div', {ariaChecked: false}))),
293+
'<div aria-checked="false"></div>',
294+
'handles booleanish attribute with `false` value correctly'
295+
)
296+
297+
t.equal(
298+
serializeNodeToHtmlString(toDom(h('div', {ariaChecked: 'mixed'}))),
299+
'<div aria-checked="mixed"></div>',
300+
'handles booleanish attribute with value correctly'
301+
)
302+
303+
t.equal(
304+
serializeNodeToHtmlString(toDom(h('div', {dataTest: false}))),
305+
'<div></div>',
306+
'ignores data properties when value is `false`'
307+
)
308+
309+
t.equal(
310+
serializeNodeToHtmlString(toDom(h('div', {dataTest: Number.NaN}))),
311+
'<div></div>',
312+
'ignores data properties when value is `NaN`'
313+
)
314+
315+
t.equal(
316+
serializeNodeToHtmlString(toDom(h('div', {dataTest: 0}))),
317+
'<div data-test="0"></div>',
318+
'encodes data properties when a number'
319+
)
320+
321+
t.equal(
322+
serializeNodeToHtmlString(toDom(h('div', {dataTest: true}))),
323+
'<div data-test=""></div>',
324+
'encodes data properties w/o value `true`'
325+
)
326+
327+
t.equal(
328+
serializeNodeToHtmlString(toDom(h('div', {dataTest: ''}))),
329+
'<div data-test=""></div>',
330+
'encodes data properties when an empty string'
331+
)
332+
333+
t.equal(
334+
serializeNodeToHtmlString(toDom(h('div', {dataTest: 'data-test'}))),
335+
'<div data-test="data-test"></div>',
336+
'encodes data properties when string'
337+
)
338+
339+
t.equal(
340+
serializeNodeToHtmlString(toDom(h('div', {data123: 'dataTest'}))),
341+
'<div data-123="dataTest"></div>',
342+
'encodes data properties when string'
343+
)
344+
345+
t.end()
346+
})
347+
348+
test('fixtures', (t) => {
349+
const root = path.join('test', 'fixtures')
350+
const fixturePaths = glob.sync(path.join(root, '**/*/'))
351+
let index = -1
352+
353+
while (++index < fixturePaths.length) {
354+
each(fixturePaths[index])
355+
}
356+
357+
t.end()
358+
359+
function each(fixturePath) {
360+
const fixture = path.relative(root, fixturePath)
361+
const fixtureInput = path.join(fixturePath, 'index.json')
362+
const fixtureOutput = path.join(fixturePath, 'index.html')
363+
const fixtureData = JSON.parse(fs.readFileSync(fixtureInput))
364+
const parsedActual = serializeNodeToHtmlString(toDom(fixtureData))
365+
let parsedExpected
366+
367+
try {
368+
parsedExpected = fs.readFileSync(fixtureOutput).toString().trim()
369+
} catch {
370+
fs.writeFileSync(fixtureOutput, parsedActual)
371+
return
372+
}
373+
374+
t.equal(parsedActual, parsedExpected, fixture)
375+
}
376+
})
377+
378+
function serializeNodeToHtmlString(node) {
379+
const serialized = serialize(node)
380+
381+
// XMLSerializer puts xmlns on “main” elements that are not in the XML
382+
// namespace.
383+
// We’d like to inspect that, but having the HTML namespace everywhere will
384+
// get unwieldy, so remove those.
385+
return serialized
386+
.replace(new RegExp(` xmlns="${webNamespaces.html}"`, 'g'), '')
387+
.replace(new RegExp(`(<(?:svg|g)) xmlns="${webNamespaces.svg}"`, 'g'), '$1')
388+
}

0 commit comments

Comments
 (0)
Please sign in to comment.