Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
cscheid committed Sep 18, 2024
2 parents 9770094 + ef8a617 commit 4e8ac8b
Show file tree
Hide file tree
Showing 83 changed files with 1,735 additions and 386 deletions.
10 changes: 10 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM mcr.microsoft.com/devcontainers/base:debian

# Install deno
ENV DENO_INSTALL=/deno
RUN mkdir -p /deno \
&& curl -fsSL https://deno.land/install.sh | sh \
&& chown -R vscode /deno

ENV PATH=${DENO_INSTALL}/bin:${PATH} \
DENO_DIR=${DENO_INSTALL}/.cache/deno
17 changes: 17 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Deno",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"editor.defaultFormatter": "denoland.vscode-deno",
"deno.lint": true,
"deno.enable": true
},
"extensions": ["denoland.vscode-deno"]
},
"remoteUser": "vscode"
}
}
8 changes: 7 additions & 1 deletion .github/workflows/deno-dom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,11 @@ jobs:
- name: Check code formatting
run: deno fmt --check --unstable

# We check in a separate step because dynamic imports
# no longer type check at runtime which we need for
# invoking the unit tests
- name: Check TypeScript types
run: deno task type-check

- name: Run tests
run: deno test --allow-read --allow-net wasm.test.ts
run: deno test --no-check --allow-read --allow-net wasm.test.ts
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ jobs:
rustup target add "$target"
# Add ARM64 Ubuntu repos
sed -E '/^deb/!d; s|.+/|deb [arch=arm64] http://ports.ubuntu.com/|' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/sources-arm64.list
sed -E '/^deb/!d; s|.+/([^/]+\.txt)?|deb [arch=arm64] http://ports.ubuntu.com/|' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/sources-arm64.list
# Mark existing repos as x86_64 only
sudo sed -i -E 's/^deb/deb [arch=amd64]/' /etc/apt/sources.list
sudo dpkg --add-architecture arm64
sudo bash -c 'apt update; true'
Expand Down
37 changes: 18 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ Rust, WASM, and obviously, Deno/TypeScript.
## Example

```typescript
import {
DOMParser,
Element,
} from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
import { DOMParser, Element } from "jsr:@b-fuze/deno-dom";

// non-JSR wasm url import: https://deno.land/x/deno_dom/deno-dom-wasm.ts
// non-JSR native url import: https://deno.land/x/deno_dom/deno-dom-native.ts

const doc = new DOMParser().parseFromString(
`
<h1>Hello World!</h1>
<p>Hello from <a href="https://deno.land/">Deno!</a></p>
`,
<h1>Hello World!</h1>
<p>Hello from <a href="https://deno.land/">Deno!</a></p>
`,
"text/html",
)!;
);

const p = doc.querySelector("p")!;

Expand All @@ -31,8 +31,11 @@ console.log(p.children[0].outerHTML); // "<b>Deno</b>"
Deno DOM has **two** backends, WASM and native using Deno native plugins. Both
APIs are **identical**, the difference being only in performance. The WASM
backend works with all Deno restrictions, but the native backend requires the
`--unstable --allow-ffi` flags. You can switch between them by importing either
`deno-dom-wasm.ts` or `deno-dom-native.ts`.
`--unstable-ffi --allow-ffi --allow-env --allow-read --allow-net=deno.land`
flags. A shorter version could be `--unstable-ffi -A`, but that allows all
permissions so you'd have to assess your risk and requirements. You can switch
between them by importing either `jsr:@b-fuze/deno-dom` for WASM or
`jsr:@b-fuze/deno-dom/native` for the native binary.

Deno DOM is still under development, but is fairly usable for basic HTML
manipulation needs.
Expand All @@ -42,23 +45,19 @@ manipulation needs.
Deno suffers an initial startup penalty in Deno DOM WASM due to Top Level Await
(TLA) preparing the WASM parser. As an alternative to running the initiation on
startup, you can initialize Deno DOM's parser on-demand yourself when you need
it by importing from `deno-dom-wasm-noinit.ts`. Example:
it by importing from `jsr:@b-fuze/deno-dom/wasm-noinit`. Example:

```typescript
// Note: -wasm-noinit.ts and not -wasm.ts
import {
DOMParser,
initParser,
} from "https://deno.land/x/deno_dom/deno-dom-wasm-noinit.ts";
import { DOMParser, initParser } from "jsr:@b-fuze/deno-dom/wasm-noinit";

// ...and when you need Deno DOM make sure you initialize the parser...
await initParser();

// Then you can use Deno DOM as you would normally
const doc = new DOMParser().parseFromString(
`
<h1>Lorem ipsum dolor...</h1>
`,
<h1>Lorem ipsum dolor...</h1>
`,
"text/html",
);
```
Expand All @@ -74,7 +73,7 @@ inconsistencies (that aren't a result of legacy APIs) file an issue.
- Fast
- Mirror _most_ supported DOM APIs as closely as possible
- Provide specific APIs in addition to DOM APIs to make certain operations more
efficient, like controlling Shadow DOM (see Open Questions)
efficient, like controlling Shadow DOM
- Use cutting-edge JS features like private class members, optional chaining,
etc

Expand Down
11 changes: 9 additions & 2 deletions build/deno-wasm/deno-wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
export function parse(html: string): string;
/**
* @param {string} html
* @param {string} context_local_name
* @returns {string}
*/
export function parse_frag(html: string): string;
export function parse_frag(html: string, context_local_name: string): string;

export type InitInput =
| RequestInfo
Expand All @@ -21,7 +22,13 @@ export type InitInput =
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly parse: (a: number, b: number, c: number) => void;
readonly parse_frag: (a: number, b: number, c: number) => void;
readonly parse_frag: (
a: number,
b: number,
c: number,
d: number,
e: number,
) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
Expand Down
38 changes: 22 additions & 16 deletions build/deno-wasm/deno-wasm.js

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions build/deno-wasm/deno-wasm_bg.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@ const lTextEncoder = typeof TextEncoder === "undefined"

let cachedTextEncoder = new lTextEncoder("utf-8");

const encodeString =
(typeof cachedTextEncoder.encodeInto === "function"
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length,
};
});
const encodeString = typeof cachedTextEncoder.encodeInto === "function"
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length,
};
};

function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
Expand Down
Binary file modified build/deno-wasm/deno-wasm_bg.wasm
Binary file not shown.
15 changes: 0 additions & 15 deletions build/deno-wasm/package.json

This file was deleted.

104 changes: 91 additions & 13 deletions deno-dom-native.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
import { Plug } from "https://deno.land/x/plug/mod.ts";
/**
* @module
*
* This module exposes the Deno DOM API with the native binary backend.
* Unlike the WASM backend the native backend requires more permissions
* due to the nature of how native bindings work. They include:
*
* - `--unstable-ffi`
* - `--allow-ffi`
* - `--allow-env`
* - `--allow-read`
* - `--allow-net=deno.land`
*
* @example
* ```ts
* import { DOMParser, initParser } from "jsr:@b-fuze/deno-dom/native";
*
* // ...and when you need Deno DOM make sure you initialize the parser...
* await initParser();
*
* // Then you can use Deno DOM as you would normally
* const doc = new DOMParser().parseFromString(
* `
* <h1>Hello World!</h1>
* <p>Hello from <a href="https://deno.land/">Deno!</a></p>
* `,
* "text/html",
* );
*
* const p = doc.querySelector("p")!;
* console.log(p.textContent); // "Hello from Deno!"
* ```
*/

import { dlopen } from "jsr:@denosaurs/plug@1.0.3";
import { register } from "./src/parser.ts";

const nativeEnv = "DENO_DOM_PLUGIN";
Expand All @@ -18,7 +52,7 @@ const _symbols = {
result: "void",
},
deno_dom_parse_frag_sync: {
parameters: ["buffer", "usize", "buffer"],
parameters: ["buffer", "usize", "buffer", "usize", "buffer"],
result: "void",
},
deno_dom_is_big_endian: { parameters: [], result: "u32" },
Expand All @@ -32,11 +66,30 @@ if (denoNativePluginPath) {
// Load the plugin locally
dylib = Deno.dlopen(denoNativePluginPath, symbols);
} else {
const host = `${Deno.build.os}-${Deno.build.arch}` as const;
let name = "";

switch (host) {
case "linux-x86_64":
case "darwin-x86_64":
case "windows-x86_64":
name = "plugin";
break;

case "linux-aarch64":
name = "plugin-linux-aarch64";
break;

default:
console.error(`deno-dom-native: host ${host} has no supported backend`);
Deno.exit(1);
}

// Download the plugin
dylib = await Plug.prepare({
name: "plugin",
dylib = await dlopen({
name,
url:
"https://github.com/b-fuze/deno-dom/releases/download/v0.1.23-alpha-artifacts/",
"https://github.com/b-fuze/deno-dom/releases/download/v0.1.41-alpha-artifacts/",
}, symbols);
}

Expand All @@ -55,16 +108,41 @@ const dylibParseFragSync = dylib.symbols.deno_dom_parse_frag_sync.bind(
const returnBufSizeLenRaw = new ArrayBuffer(usizeBytes * 2);
const returnBufSizeLen = new Uint8Array(returnBufSizeLenRaw);

type DocumentParser = (
srcBuf: Uint8Array,
srcLength: bigint,
returnBuf: Uint8Array,
) => void;
type FragmentParser = (
srcBuf: Uint8Array,
srcLength: bigint,
contextLocalNameBuf: Uint8Array,
contextLocalNameLength: bigint,
returnBuf: Uint8Array,
) => void;

function genericParse(
parser: (
srcBuf: Uint8Array,
srcLength: number,
returnBuf: Uint8Array,
) => void,
parser: DocumentParser | FragmentParser,
srcHtml: string,
contextLocalName?: string,
): string {
const encodedHtml = utf8Encoder.encode(srcHtml);
parser(encodedHtml, encodedHtml.length, returnBufSizeLen);
if (contextLocalName) {
const encodedContextLocalName = utf8Encoder.encode(contextLocalName);
(parser as FragmentParser)(
encodedHtml,
BigInt(encodedHtml.length),
encodedContextLocalName,
BigInt(encodedContextLocalName.length),
returnBufSizeLen,
);
} else {
(parser as DocumentParser)(
encodedHtml,
BigInt(encodedHtml.length),
returnBufSizeLen,
);
}

const outBufSize = Number(
new DataView(returnBufSizeLenRaw).getBigUint64(0, !isBigEndian),
Expand All @@ -79,8 +157,8 @@ function parse(html: string): string {
return genericParse(dylibParseSync, html);
}

function parseFrag(html: string): string {
return genericParse(dylibParseFragSync, html);
function parseFrag(html: string, contextLocalName?: string): string {
return genericParse(dylibParseFragSync, html, contextLocalName);
}

// Register parse function
Expand Down
37 changes: 35 additions & 2 deletions deno-dom-wasm-noinit.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
/**
* @module
*
* This module exposes the Deno DOM API with the WASM (Web Assembly) backend.
* This module is different from the primary WASM module because it allows
* you to control when the WASM HTML parsing engine loads (which is a
* relatively slow process). You can't use any of the parsing functions
* of Deno DOM until you invoke the async `initParser()` export.
*
* @example
* ```ts
* import { DOMParser, initParser } from "jsr:@b-fuze/deno-dom/wasm-noinit";
*
* // ...and when you need Deno DOM's parser make sure you initialize it...
* await initParser();
*
* // Then you can use Deno DOM as you would normally
* const doc = new DOMParser().parseFromString(
* `
* <h1>Hello World!</h1>
* <p>Hello from <a href="https://deno.land/">Deno!</a></p>
* `,
* "text/html",
* );
*
* const p = doc.querySelector("p")!;
* console.log(p.textContent); // "Hello from Deno!"
* ```
*/

import initWasm, {
parse,
parse_frag as parseFrag,
} from "./build/deno-wasm/deno-wasm.js";
import { register } from "./src/parser.ts";
import { type Parser, register } from "./src/parser.ts";

export async function initParser() {
await initWasm();
register(parse, parseFrag);
register(
parse,
parseFrag as Parser,
);
}

export * from "./src/api.ts";
Loading

0 comments on commit 4e8ac8b

Please sign in to comment.