Skip to content

Commit

Permalink
feat: use jsdom instead of happydom
Browse files Browse the repository at this point in the history
Related to #355
  • Loading branch information
Akryum committed Dec 2, 2022
1 parent edf46b3 commit e4213d3
Show file tree
Hide file tree
Showing 6 changed files with 436 additions and 352 deletions.
2 changes: 1 addition & 1 deletion packages/histoire/package.json
Expand Up @@ -55,8 +55,8 @@
"fs-extra": "^10.0.1",
"globby": "^13.1.1",
"gray-matter": "^4.0.3",
"happy-dom": "^2.55.0",
"jiti": "^1.16.0",
"jsdom": "^20.0.3",
"markdown-it": "^12.3.2",
"markdown-it-anchor": "^8.6.2",
"markdown-it-attrs": "^4.1.3",
Expand Down
3 changes: 0 additions & 3 deletions packages/histoire/src/node/collect/worker.ts
Expand Up @@ -60,9 +60,6 @@ export default async (payload: Payload): Promise<ReturnData> => {
}

const { destroy: destroyDomEnv } = createDomEnv()
if (!global.CSS.supports) {
global.CSS.supports = () => false
}

const el = window.document.createElement('div')

Expand Down
13 changes: 13 additions & 0 deletions packages/histoire/src/node/dom/dom-keys.ts
Expand Up @@ -24,6 +24,7 @@ const LIVING_KEYS = [
'Comment',
'DocumentType',
'NodeList',
'RadioNodeList',
'HTMLCollection',
'HTMLOptionsCollection',
'DOMStringMap',
Expand Down Expand Up @@ -100,6 +101,7 @@ const LIVING_KEYS = [
'HTMLVideoElement',
'HTMLAudioElement',
'HTMLTrackElement',
'HTMLFormControlsCollection',
'SVGElement',
'SVGGraphicsElement',
'SVGSVGElement',
Expand Down Expand Up @@ -130,6 +132,7 @@ const LIVING_KEYS = [
'Location',
'History',
'Screen',
'Crypto',
'Performance',
'Navigator',
'PluginArray',
Expand Down Expand Up @@ -163,6 +166,12 @@ const LIVING_KEYS = [
'Headers',
'AbortController',
'AbortSignal',
'ArrayBuffer',

// not specified in docs, but is available
'Image',
'Audio',
'Option',
]

const OTHER_KEYS = [
Expand All @@ -171,6 +180,7 @@ const OTHER_KEYS = [
'atob',
'blur',
'btoa',
'cancelAnimationFrame',
/* 'clearInterval', */
/* 'clearTimeout', */
'close',
Expand All @@ -187,6 +197,7 @@ const OTHER_KEYS = [
'innerWidth',
'length',
'location',
'matchMedia',
'moveBy',
'moveTo',
'name',
Expand All @@ -201,6 +212,7 @@ const OTHER_KEYS = [
'print',
'prompt',
'removeEventListener',
'requestAnimationFrame',
'resizeBy',
'resizeTo',
'screen',
Expand All @@ -221,6 +233,7 @@ const OTHER_KEYS = [
'stop',
/* 'toString', */
'top',
'Window',
'window',
]

Expand Down
35 changes: 21 additions & 14 deletions packages/histoire/src/node/dom/env.ts
@@ -1,22 +1,29 @@
import { Window } from 'happy-dom'
import { KEYS } from './dom-keys.js'
import {
JSDOM,
VirtualConsole,
} from 'jsdom'
import { populateGlobal } from './util.js'

export function createDomEnv () {
// @ts-ignore
const window = global.window = new Window()
const globalKeys = KEYS.concat(Object.getOwnPropertyNames(window))
.filter(k => !k.startsWith('_'))
.filter(k => !(k in global))
const dom = new JSDOM(
'<!DOCTYPE html>',
{
pretendToBeVisual: true,
runScripts: 'dangerously',
url: 'http://localhost:3000',
virtualConsole: console && global.console ? new VirtualConsole().sendTo(global.console) : undefined,
includeNodeLocations: false,
contentType: 'text/html',
},
)

for (const key of globalKeys) {
global[key] = window[key]
}
const { keys, originals } = populateGlobal(global, dom.window, { bindFunctions: true })

function destroy () {
globalKeys.forEach(key => delete global[key])
globalKeys.length = 0
window.happyDOM.cancelAsync()
delete global.window
keys.forEach(key => delete global[key])
originals.forEach((v, k) => {
global[k] = v
})
}

return {
Expand Down
83 changes: 83 additions & 0 deletions packages/histoire/src/node/dom/util.ts
@@ -0,0 +1,83 @@
/*
* Code from vitest https://github.com/vitest-dev/vitest/blob/98974ba4a5b95a57a98bc2077b74bc5609d6bb3a/packages/vitest/src/integrations/env/utils.ts#L1 by @antfu and @patak
*/

import { KEYS } from './dom-keys.js'

const skipKeys = [
'window',
'self',
'top',
'parent',
]

export function getWindowKeys (global: any, win: any) {
const keys = new Set(KEYS.concat(Object.getOwnPropertyNames(win))
.filter((k) => {
if (skipKeys.includes(k)) {
return false
}
if (k in global) {
return KEYS.includes(k)
}
return true
}))

return keys
}

function isClassLikeName (name: string) {
return name[0] === name[0].toUpperCase()
}

interface PopulateOptions {
// we bind functions such as addEventListener and others
// because they rely on `this` in happy-dom, and in jsdom it
// has a priority for getting implementation from symbols
// (global doesn't have these symbols, but window - does)
bindFunctions?: boolean
}

export function populateGlobal (global: any, win: any, options: PopulateOptions = {}) {
const { bindFunctions = false } = options
const keys = getWindowKeys(global, win)

const originals = new Map<string | symbol, any>()

const overrideObject = new Map<string | symbol, any>()
for (const key of keys) {
const boundFunction = bindFunctions &&
typeof win[key] === 'function' &&
!isClassLikeName(key) &&
win[key].bind(win)

if (KEYS.includes(key) && key in global) { originals.set(key, global[key]) }

Object.defineProperty(global, key, {
get () {
if (overrideObject.has(key)) { return overrideObject.get(key) }
if (boundFunction) { return boundFunction }
return win[key]
},
set (v) {
overrideObject.set(key, v)
},
configurable: true,
})
}

global.window = global
global.self = global
global.top = global
global.parent = global

if (global.global) { global.global = global }

skipKeys.forEach(k => keys.add(k))

return {
keys,
skipKeys,
originals,
}
}

0 comments on commit e4213d3

Please sign in to comment.