Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSDOM port #542

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions jsdom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
!package.json
*.bak
18 changes: 18 additions & 0 deletions jsdom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# jsdom

Port of [jsdom](https://github.com/jsdom/jsdom).

> jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/) Standards, for use with ~~Node.js~~ Deno. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.

## Usage

```typescript
import { JSDOM } from "https://deno.land/std/jsdom/mod.ts";

let dom = new JSDOM(`
<!DOCTYPE html>
<p>Hello, Deno!</p>
`);
let p = dom.window.document.querySelector("p");
console.log(p.textContent); // => "Hello, Deno!"
```
8 changes: 8 additions & 0 deletions jsdom/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { JSDOM } from "./mod.ts";

let dom = new JSDOM(`
<!DOCTYPE html>
<p>Hello, Deno!</p>
`);
let p = dom.window.document.querySelector("p");
console.log(p.textContent); // => "Hello, Deno!"
4 changes: 4 additions & 0 deletions jsdom/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { jsdom as instance } from "./vendor/jsdom.js";
import { jsdom } from "./types/jsdom.ts";

export const JSDOM = instance.JSDOM as jsdom.JSDOM;
Copy link

Choose a reason for hiding this comment

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

Another implementation of this that should also work:

export { jsdom as JSDOM } from "./vendor/jsdom.js";
export { jsdom as JSDOM } from "./types/jsdom.ts";

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, @j-f1. Unfortunately, this doesn't work on my machine. When I change it, JSDOM can't be imported anymore.

$ deno test.ts
Compile file:///Users/marktiedemann/dev/deno_std/jsdom/mod.ts
error: Uncaught SyntaxError: The requested module './mod.ts' does not provide an export named 'JSDOM'

Copy link

Choose a reason for hiding this comment

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

Well, that’s unexpected. Sorry to send you on a wild goose chase!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, @j-f1.

I just tried again, with the same code, and got a different error:

$ deno test.ts
Compile file:///Users/marktiedemann/dev/deno_std/jsdom/mod.ts

error TS2300: Duplicate identifier 'JSDOM'.

► file:///Users/marktiedemann/dev/deno_std/jsdom/mod.ts:1:19

1 export { jsdom as JSDOM } from "./vendor/jsdom.js";
                    ~~~~~

error TS2300: Duplicate identifier 'JSDOM'.

► file:///Users/marktiedemann/dev/deno_std/jsdom/mod.ts:2:19

2 export { jsdom as JSDOM } from "./types/jsdom.ts";
                    ~~~~~


Found 2 errors.

This seems like a more expected error, still unfortunate.

Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect denoland/deno#2746 will fix this (or at least make it possible).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kitsonk, awesome! As soon as denoland/deno#2746 is released, I will update the types here.

Copy link
Contributor

Choose a reason for hiding this comment

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

.d.ts support is in 0.16.

10 changes: 10 additions & 0 deletions jsdom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"scripts": {
"vendor": "webpack-cli && sed -i.bak '1s;^;export ;' ./vendor/jsdom.js"
},
"devDependencies": {
"jsdom": "15.1.1",
"webpack": "4.36.1",
"webpack-cli": "3.3.6"
}
}
MarkTiedemann marked this conversation as resolved.
Show resolved Hide resolved
39 changes: 39 additions & 0 deletions jsdom/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { JSDOM } from "./mod.ts";
import { assertEquals } from "../testing/asserts.ts";
import { test, runIfMain } from "../testing/mod.ts";

test(function helloWorld(): void {
let dom = new JSDOM("<!DOCTYPE html><p>Hello world</p>");
let text = dom.window.document.querySelector("p").textContent;
assertEquals(text, "Hello world");
});

test(function runScripts(): void {
let dom = new JSDOM(
// eslint-disable-next-line max-len
'<body><script>document.body.appendChild(document.createElement("hr"));</script></body>',
{ runScripts: "dangerously" }
);
let children = dom.window.document.body.children.length;
assertEquals(children, 2);
});

test(function serialize(): void {
let dom = new JSDOM("<!DOCTYPE html>hello");
assertEquals(
dom.serialize(),
"<!DOCTYPE html><html><head></head><body>hello</body></html>"
);
assertEquals(
dom.window.document.documentElement.outerHTML,
"<html><head></head><body>hello</body></html>"
);
});

test(function fragment(): void {
let frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);
assertEquals(frag.childNodes.length, 2);
assertEquals(frag.querySelector("strong").textContent, "Hi!");
});

runIfMain(import.meta);
27 changes: 27 additions & 0 deletions jsdom/types/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// TODO: Generate proper DOM types using https://github.com/microsoft/TSJS-lib-generator.

export namespace dom {
export interface Window {
document: Document;
}

export interface Document extends Node {
body: HTMLElement;
documentElement: HTMLElement;
}

export interface DocumentFragment extends Node {}

export interface HTMLElement extends Node {
children: HTMLElement[];
outerHTML: string;
}

export interface Node {
childNodes: ChildNode[];
querySelector(query: string): HTMLElement;
textContent: string | null;
}

export interface ChildNode extends Node {}
}
29 changes: 29 additions & 0 deletions jsdom/types/jsdom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// TODO: Use proper types from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jsdom/index.d.ts.

import { dom } from "./dom.ts";

export namespace jsdom {
export interface JSDOM {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new (
html: string | BinaryData,
options?: { runScripts?: "dangerously" }
): JSDOM;
window: dom.Window;
fragment: (html: string) => dom.DocumentFragment;
serialize(): string;
}

export type BinaryData = ArrayBuffer | DataView | TypedArray;

export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;
}
603 changes: 603 additions & 0 deletions jsdom/vendor/jsdom.js

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions jsdom/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const path = require("path");

module.exports = {
entry: "./node_modules/jsdom/lib/api.js",
output: {
path: path.join(__dirname, "vendor"),
filename: "./jsdom.js",
library: "jsdom",
libraryTarget: "var"
},
mode: "production",
node: {
child_process: "empty",
fs: "empty",
net: "empty",
tls: "empty"
},
performance: {
// TODO: Investigate whether the bundle size can be optimized.
hints: false
}
};