diff --git a/package-lock.json b/package-lock.json
index abad5394c..5ae53c2b0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3680,6 +3680,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -3805,6 +3824,29 @@
"node-int64": "^0.4.0"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -6397,6 +6439,10 @@
"resolved": "packages/happy-dom",
"link": true
},
+ "node_modules/happy-dom-without-nodejs": {
+ "resolved": "packages/happy-dom-without-nodejs",
+ "link": true
+ },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -6698,6 +6744,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -9409,10 +9474,9 @@
}
},
"node_modules/punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
- "dev": true,
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"engines": {
"node": ">=6"
}
@@ -10912,6 +10976,17 @@
"node": ">=6"
}
},
+ "node_modules/tr46": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+ "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/ts-jest": {
"version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
@@ -11725,6 +11800,18 @@
"node": ">=12"
}
},
+ "node_modules/whatwg-url": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+ "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
+ "dependencies": {
+ "tr46": "^5.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -11925,7 +12012,6 @@
"dependencies": {
"css.escape": "^1.5.1",
"entities": "^4.5.0",
- "iconv-lite": "^0.6.3",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0"
@@ -11943,6 +12029,31 @@
"vitest": "^0.32.4"
}
},
+ "packages/happy-dom-without-nodejs": {
+ "version": "0.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "css.escape": "^1.5.1",
+ "entities": "^4.5.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "devDependencies": {
+ "@types/css.escape": "^1.5.0",
+ "@types/node": "^16.11.7",
+ "@types/node-fetch": "^2.6.1",
+ "@typescript-eslint/eslint-plugin": "^5.16.0",
+ "@typescript-eslint/parser": "^5.16.0",
+ "@vitest/ui": "^0.33.0",
+ "@webref/css": "6.6.2",
+ "prettier": "^2.6.0",
+ "typescript": "^5.0.4",
+ "vitest": "^0.32.4"
+ }
+ },
"packages/integration-test": {
"name": "@happy-dom/integration-test",
"version": "0.0.0",
diff --git a/packages/happy-dom-without-nodejs/.editorconfig b/packages/happy-dom-without-nodejs/.editorconfig
new file mode 100755
index 000000000..ec8afa677
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/.editorconfig
@@ -0,0 +1,8 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Indentation override for all JS under lib directory
+[{**.js,**.ts,**.json}]
+indent_style = tab
\ No newline at end of file
diff --git a/packages/happy-dom-without-nodejs/.gitignore b/packages/happy-dom-without-nodejs/.gitignore
new file mode 100755
index 000000000..48f00e915
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+tmp
+lib
+src
+cjs
+.DS_Store
\ No newline at end of file
diff --git a/packages/happy-dom-without-nodejs/.npmignore b/packages/happy-dom-without-nodejs/.npmignore
new file mode 100644
index 000000000..630ba6a00
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/.npmignore
@@ -0,0 +1,7 @@
+node_modules
+tmp
+test
+bin
+polyfills
+.turbo
+npm-shrinkwrap.json
\ No newline at end of file
diff --git a/packages/happy-dom-without-nodejs/.prettierrc.cjs b/packages/happy-dom-without-nodejs/.prettierrc.cjs
new file mode 100644
index 000000000..e66b0dcab
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/.prettierrc.cjs
@@ -0,0 +1,6 @@
+module.exports = {
+ singleQuote: true,
+ useTabs: true,
+ printWidth: 100,
+ trailingComma: 'none'
+};
\ No newline at end of file
diff --git a/packages/happy-dom-without-nodejs/LICENSE b/packages/happy-dom-without-nodejs/LICENSE
new file mode 100644
index 000000000..eebd5782b
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 David Ortner (capricorn86)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/happy-dom-without-nodejs/README.md b/packages/happy-dom-without-nodejs/README.md
new file mode 100644
index 000000000..20e55fbe4
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/README.md
@@ -0,0 +1,131 @@
+![Happy DOM Logo](https://github.com/capricorn86/happy-dom/raw/master/docs/happy-dom-logo.jpg)
+
+# About
+
+[Happy DOM](https://github.com/capricorn86/happy-dom) is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/).
+
+The goal of [Happy DOM](https://github.com/capricorn86/happy-dom) is to emulate enough of a web browser to be useful for testing, scraping web sites and server-side rendering.
+
+[Happy DOM](https://github.com/capricorn86/happy-dom) focuses heavily on performance and can be used as an alternative to [JSDOM](https://github.com/jsdom/jsdom).
+
+### DOM Features
+
+- Custom Elements (Web Components)
+
+- Shadow Root (Shadow DOM)
+
+- Declarative Shadow DOM
+
+- Mutation Observer
+
+- Tree Walker
+
+- Fetch
+
+And much more..
+
+### Works With
+
+- [Google LitHTML](https://lit-html.polymer-project.org)
+
+- [Google LitElement](https://lit-element.polymer-project.org)
+
+- [React](https://reactjs.org)
+
+- [Angular](https://angular.io/)
+
+- [Vue](https://vuejs.org/)
+
+### Module Systems
+
+- [ESM](https://nodejs.org/api/esm.html#introduction)
+- [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules)
+
+# Installation
+
+```bash
+npm install happy-dom
+```
+
+# Usage
+
+Happy DOM can be used as a simulated [Browser](https://github.com/capricorn86/happy-dom/wiki/Browser) or by using the [Window](https://github.com/capricorn86/happy-dom/wiki/Window) class directly to quickly setup up a DOM.
+
+## Window
+
+```javascript
+import { Window } from 'happy-dom';
+
+const window = new Window({ url: 'https://localhost:8080' });
+const document = window.document;
+
+document.body.innerHTML = '
';
+
+const container = document.querySelector('.container');
+const button = document.createElement('button');
+
+container.appendChild(button);
+
+// Outputs ""
+console.log(document.body.innerHTML);
+```
+
+## Browser
+
+```javascript
+import { Browser, BrowserErrorCaptureEnum } from 'happy-dom';
+
+const browser = new Browser({ settings: { errorCapture: BrowserErrorCaptureEnum.processLevel } });
+const page = browser.newPage();
+
+// Navigates page
+await page.goto('https://github.com/capricorn86');
+
+// Clicks on link
+page.mainFrame.document.querySelector('a[href*="capricorn86/happy-dom"]').click();
+
+// Waits for all operations on the page to complete (fetch, timers etc.)
+await page.waitUntilComplete();
+
+// Outputs "GitHub - capricorn86/happy-dom: Happy DOM..."
+console.log(page.mainFrame.document.title);
+
+// Closes the browser
+await browser.close();
+```
+
+# Documentation
+
+Read more about how to use Happy DOM in our [Wiki](https://github.com/capricorn86/happy-dom/wiki).
+
+# Performance
+
+| Operation | JSDOM | Happy DOM |
+| ------------------------------------ | ------- | --------- |
+| Import / Require | 333 ms | 45 ms |
+| Parse HTML | 256 ms | 26 ms |
+| Serialize HTML | 65 ms | 8 ms |
+| Render custom element | 214 ms | 19 ms |
+| querySelectorAll('tagname') | 4.9 ms | 0.7 ms |
+| querySelectorAll('.class') | 6.4 ms | 3.7 ms |
+| querySelectorAll('[attribute]') | 4.0 ms | 1.7 ms |
+| querySelectorAll('[class~="name"]') | 5.5 ms | 2.9 ms |
+| querySelectorAll(':nth-child(2n+1)') | 10.4 ms | 3.8 ms |
+
+See how the test was done [here](https://github.com/capricorn86/happy-dom-performance-test)
+
+# Jest
+
+Happy DOM provide with a package called [@happy-dom/jest-environment](https://github.com/capricorn86/happy-dom/tree/master/packages/jest-environment) that makes it possible to use Happy DOM with [Jest](https://jestjs.io/).
+
+# Vitest
+
+[Vitest](https://github.com/vitest-dev/vitest) supports Happy DOM out of the box.
+
+# Global Registration
+
+Happy DOM provide with a package called [@happy-dom/global-registrator](https://github.com/capricorn86/happy-dom/tree/master/packages/global-registrator) that can register Happy DOM globally. It makes it possible to use Happy DOM for testing in a Node environment.
+
+# Sponsors
+
+[](https://rtvision.com)
diff --git a/packages/happy-dom-without-nodejs/bin/polyfill-source.cjs b/packages/happy-dom-without-nodejs/bin/polyfill-source.cjs
new file mode 100644
index 000000000..bbd12f178
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/bin/polyfill-source.cjs
@@ -0,0 +1,138 @@
+/* eslint-disable no-console*/
+/* eslint-disable @typescript-eslint/no-var-requires*/
+
+const Path = require('path');
+const FS = require('fs');
+
+process.on('unhandledRejection', (reason) => {
+ console.error(reason);
+ process.exit(1);
+});
+
+main();
+
+const POLYFILL_MODULES = ['net', 'crypto', 'url', 'stream', 'vm'];
+
+const POLYFILLS = [
+ function polyfillProcess(_directory, file, content) {
+ if (!file.endsWith('.cjs') && !file.endsWith('.js')) {
+ return content;
+ }
+ return content
+ .replace(/process\.platform/gm, `'Unknown'`)
+ .replace(/process\.arch/gm, `'Unknown'`);
+ },
+ function polyfillNodeJS(_directory, _file, content) {
+ return content.replace(/NodeJS.Timeout/gm, `number`).replace(/NodeJS.Immediate/gm, `number`);
+ },
+ function polyfillFetch(_directory, file, content) {
+ if (file.endsWith('/Fetch.d.ts') || file.endsWith('/SyncFetch.d.ts')) {
+ return `export default class Fetch { send(): Promise; }`;
+ }
+ if (file.endsWith('/Fetch.js') || file.endsWith('/SyncFetch.js')) {
+ return `export default class Fetch { send() { throw Error('Fetch is not supported without Node.js.'); } }`;
+ }
+ if (file.endsWith('/Fetch.cjs') || file.endsWith('/SyncFetch.cjs')) {
+ return `class Fetch { send() { throw Error('Fetch is not supported without Node.js.'); } }\n\nmodule.exports = Fetch;\nmodule.exports.default = Fetch;`;
+ }
+ return content;
+ },
+ function polyfillTypescriptDefinition(_directory, file, content) {
+ if (!file.endsWith('.d.ts')) {
+ return content;
+ }
+
+ return content.replace(
+ /\/\/\/\s*/gm,
+ ''
+ );
+ },
+ function polyfillModules(directory, file, content) {
+ const polyfillDirectory = Path.join(directory, 'polyfills');
+ for (const module of POLYFILL_MODULES) {
+ const regexp = new RegExp(`import.+from\\s*(["']${module}["'])`);
+ moduleMatch = content.match(regexp);
+ if (moduleMatch) {
+ content = content.replace(
+ moduleMatch[0],
+ moduleMatch[0].replace(
+ moduleMatch[1] || moduleMatch[2],
+ `'${Path.relative(Path.dirname(file), Path.join(polyfillDirectory, `${module}.js`))}'`
+ )
+ );
+ }
+ }
+ return content;
+ }
+];
+
+function getArguments() {
+ const args = {
+ dir: null
+ };
+
+ for (const arg of process.argv) {
+ if (arg.startsWith('--dir=')) {
+ args.dir = arg.split('=')[1];
+ }
+ }
+
+ return args;
+}
+
+async function readDirectory(directory) {
+ const files = await FS.promises.readdir(directory);
+ const statsPromises = [];
+ let allFiles = [];
+
+ for (const file of files) {
+ const filePath = Path.join(directory, file);
+ statsPromises.push(
+ FS.promises.stat(filePath).then((stats) => {
+ if (stats.isDirectory()) {
+ return readDirectory(filePath).then((files) => (allFiles = allFiles.concat(files)));
+ }
+ const extname = Path.extname(filePath);
+ if (extname === '.js' || extname === '.cjs' || extname === '.ts') {
+ allFiles.push(filePath);
+ }
+ })
+ );
+ }
+
+ await Promise.all(statsPromises);
+
+ return allFiles;
+}
+
+async function polyfillFiles(directory, files) {
+ const writePromises = [];
+
+ for (const file of files) {
+ writePromises.push(
+ FS.promises.readFile(file).then((content) => {
+ const oldContent = content.toString();
+ let newContent = oldContent;
+ for (const polyfill of POLYFILLS) {
+ newContent = polyfill(directory, file, newContent);
+ }
+ if (newContent === oldContent) {
+ return;
+ }
+ return FS.promises.writeFile(file, newContent);
+ })
+ );
+ }
+
+ await Promise.all(writePromises);
+}
+
+async function main() {
+ const args = getArguments();
+ if (!args.dir) {
+ throw new Error('Invalid arguments');
+ }
+ const directory = Path.resolve(args.dir);
+ const files = await readDirectory(directory);
+ await polyfillFiles(directory, files);
+}
diff --git a/packages/happy-dom-without-nodejs/package-lock.json b/packages/happy-dom-without-nodejs/package-lock.json
new file mode 100644
index 000000000..35ccb79a0
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/package-lock.json
@@ -0,0 +1,30 @@
+{
+ "name": "happy-dom",
+ "version": "0.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "happy-dom",
+ "version": "0.0.0",
+ "license": "MIT",
+ "devDependencies": {
+ "@types/node": "^18.11.15"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "18.11.15",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz",
+ "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==",
+ "dev": true
+ }
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "18.11.15",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz",
+ "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==",
+ "dev": true
+ }
+ }
+}
diff --git a/packages/happy-dom-without-nodejs/package.json b/packages/happy-dom-without-nodejs/package.json
new file mode 100644
index 000000000..5f2ec2f51
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "happy-dom-without-nodejs",
+ "version": "0.0.0",
+ "license": "MIT",
+ "homepage": "https://github.com/capricorn86/happy-dom/packages/happy-dom-without-nodejs",
+ "repository": "https://github.com/capricorn86/happy-dom",
+ "author": "David Ortner",
+ "description": "Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.",
+ "main": "lib/index.js",
+ "type": "module",
+ "exports": {
+ ".": {
+ "import": "./lib/index.js",
+ "default": "./lib/index.js"
+ },
+ "./lib/*.js": {
+ "import": "./lib/*.js",
+ "default": "./lib/*.js"
+ },
+ "./lib/*.ts": {
+ "import": "./lib/*.ts",
+ "default": "./lib/*.ts"
+ },
+ "./lib/*.map": {
+ "import": "./lib/*.map",
+ "default": "./lib/*.map"
+ },
+ "./src/*.ts": "./src/*.ts",
+ "./package.json": "./package.json",
+ "./.eslintrc.cjs": "./.eslintrc.cjs",
+ "./.prettierrc.cjs": "./.prettierrc.cjs"
+ },
+ "keywords": [
+ "jsdom",
+ "dom",
+ "browser",
+ "custom",
+ "elements",
+ "web",
+ "components",
+ "html",
+ "whatwg",
+ "w3c"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "compile": "rm -rf ./lib ./src && cp -r ../happy-dom/lib ./lib && cp -r ../happy-dom/src ./src && cp -r ./polyfills ./lib && npm run polyfill",
+ "polyfill": "node ./bin/polyfill-source.cjs --dir=./lib",
+ "test": "tsc --noEmit"
+ },
+ "dependencies": {
+ "css.escape": "^1.5.1",
+ "entities": "^4.5.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^14.0.0",
+ "buffer": "^6.0.3"
+ },
+ "devDependencies": {
+ "@types/css.escape": "^1.5.0",
+ "@types/node": "^16.11.7",
+ "@types/node-fetch": "^2.6.1",
+ "@typescript-eslint/eslint-plugin": "^5.16.0",
+ "@typescript-eslint/parser": "^5.16.0",
+ "@vitest/ui": "^0.33.0",
+ "@webref/css": "6.6.2",
+ "prettier": "^2.6.0",
+ "typescript": "^5.0.4",
+ "vitest": "^0.32.4"
+ }
+}
diff --git a/packages/happy-dom-without-nodejs/polyfills/crypto.js b/packages/happy-dom-without-nodejs/polyfills/crypto.js
new file mode 100644
index 000000000..a7c1a183b
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/polyfills/crypto.js
@@ -0,0 +1,3 @@
+const webcrypto = globalThis.crypto;
+
+export { webcrypto };
diff --git a/packages/happy-dom-without-nodejs/polyfills/net.js b/packages/happy-dom-without-nodejs/polyfills/net.js
new file mode 100644
index 000000000..ec5b8c0fe
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/polyfills/net.js
@@ -0,0 +1,18 @@
+function isIP(ip) {
+ const ipv4 =
+ /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ const ipv6 =
+ /^(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))$/;
+
+ if (ipv4.test(ip)) {
+ return 4;
+ }
+
+ if (ipv6.test(ip)) {
+ return 6;
+ }
+
+ return 0;
+}
+
+export { isIP };
diff --git a/packages/happy-dom-without-nodejs/polyfills/stream.js b/packages/happy-dom-without-nodejs/polyfills/stream.js
new file mode 100644
index 000000000..cb81dbbd9
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/polyfills/stream.js
@@ -0,0 +1,10 @@
+const Readable = globalThis.ReadableStream;
+const Writable = globalThis.WritableStream;
+const Transform = globalThis.TransformStream;
+
+export { Readable, Writable, Transform };
+export default {
+ Readable,
+ Writable,
+ Transform
+};
diff --git a/packages/happy-dom-without-nodejs/polyfills/url.js b/packages/happy-dom-without-nodejs/polyfills/url.js
new file mode 100644
index 000000000..a3eca0d9e
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/polyfills/url.js
@@ -0,0 +1,3 @@
+import { URL, URLSearchParams, UrlObject } from 'whatwg-url';
+
+export { URL, URLSearchParams, UrlObject };
diff --git a/packages/happy-dom-without-nodejs/polyfills/vm.js b/packages/happy-dom-without-nodejs/polyfills/vm.js
new file mode 100644
index 000000000..05800614c
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/polyfills/vm.js
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+class Script {
+ /**
+ *
+ */
+ runInContext() {}
+}
+const isContext = () => true;
+const createContext = (context) => context;
+export { Script, isContext, createContext };
diff --git a/packages/happy-dom-without-nodejs/tsconfig.json b/packages/happy-dom-without-nodejs/tsconfig.json
new file mode 100644
index 000000000..1aaa25cfe
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "rootDir": "lib",
+ "target": "ES2020",
+ "declaration": true,
+ "declarationMap": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "esModuleInterop": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "resolveJsonModule": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "removeComments": false,
+ "preserveConstEnums": true,
+ "sourceMap": true,
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "composite": false,
+ "incremental": false,
+ "allowJs": true,
+ "lib": [
+ "es2020",
+ "dom"
+ ],
+ "types": []
+ },
+ "include": [
+ "lib"
+ ],
+ "exclude": [
+ "@types/dom"
+ ]
+}
\ No newline at end of file
diff --git a/packages/happy-dom-without-nodejs/vitest.config.ts b/packages/happy-dom-without-nodejs/vitest.config.ts
new file mode 100644
index 000000000..554c98429
--- /dev/null
+++ b/packages/happy-dom-without-nodejs/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ environment: 'node',
+ include: ['./test/**/*.test.ts'],
+ setupFiles: ['./test/setup.ts'],
+ testTimeout: 500
+ }
+});
diff --git a/packages/happy-dom/README.md b/packages/happy-dom/README.md
index 741ed234b..20e55fbe4 100644
--- a/packages/happy-dom/README.md
+++ b/packages/happy-dom/README.md
@@ -49,7 +49,7 @@ npm install happy-dom
# Usage
-A simple example of how you can use Happy DOM.
+Happy DOM can be used as a simulated [Browser](https://github.com/capricorn86/happy-dom/wiki/Browser) or by using the [Window](https://github.com/capricorn86/happy-dom/wiki/Window) class directly to quickly setup up a DOM.
## Window
diff --git a/packages/happy-dom/package.json b/packages/happy-dom/package.json
index 1d43d30fe..331fb575b 100644
--- a/packages/happy-dom/package.json
+++ b/packages/happy-dom/package.json
@@ -77,7 +77,6 @@
"dependencies": {
"css.escape": "^1.5.1",
"entities": "^4.5.0",
- "iconv-lite": "^0.6.3",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0"
diff --git a/packages/happy-dom/src/console/VirtualConsole.ts b/packages/happy-dom/src/console/VirtualConsole.ts
index 56232288b..d227124d7 100644
--- a/packages/happy-dom/src/console/VirtualConsole.ts
+++ b/packages/happy-dom/src/console/VirtualConsole.ts
@@ -2,7 +2,6 @@ import IVirtualConsolePrinter from './types/IVirtualConsolePrinter.js';
import VirtualConsoleLogLevelEnum from './enums/VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from './enums/VirtualConsoleLogTypeEnum.js';
import IVirtualConsoleLogGroup from './types/IVirtualConsoleLogGroup.js';
-import * as PerfHooks from 'perf_hooks';
import { ConsoleConstructor } from 'console';
/**
@@ -276,7 +275,7 @@ export default class VirtualConsole implements Console {
* @param [label=default] Label.
*/
public time(label = 'default'): void {
- this.#time[label] = PerfHooks.performance.now();
+ this.#time[label] = performance.now();
}
/**
@@ -288,7 +287,7 @@ export default class VirtualConsole implements Console {
public timeEnd(label = 'default'): void {
const time = this.#time[label];
if (time) {
- const duration = PerfHooks.performance.now() - time;
+ const duration = performance.now() - time;
this.#printer.print({
type: VirtualConsoleLogTypeEnum.timeEnd,
level: VirtualConsoleLogLevelEnum.info,
@@ -308,7 +307,7 @@ export default class VirtualConsole implements Console {
public timeLog(label = 'default', ...args: Array