wasm-quickjs-dom 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/edge-js-payload.js +1 -1
- package/dist/index.js +52 -3
- package/dist/quickjs.wasm +0 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A small JSDOM for runtimes without Node: a page's scripts run in QuickJS (compiled to WebAssembly) on a
|
|
2
|
+
* A small JSDOM for runtimes without Node: a page's scripts run in QuickJS (compiled to WebAssembly) on a DOM written in C.
|
|
3
3
|
*
|
|
4
4
|
* Runs wherever WebAssembly does, `eval` or not, so it also works on Cloudflare Workers, Vercel Edge, Deno, Bun and
|
|
5
5
|
* browsers. The guest program (`dist/edge-js-payload.js`, built by `scripts/build-edge-js.mjs`) holds the DOM and the
|
|
@@ -134,7 +134,52 @@ export async function createDOM(source, options = {}) {
|
|
|
134
134
|
// ES modules: QuickJS loads them synchronously, so the host fetches a module's whole import graph first
|
|
135
135
|
// (static imports and `import("literal")`), then serves the sources from memory
|
|
136
136
|
const modules = new Map();
|
|
137
|
+
// import maps (`<script type="importmap">`): specifier prefixes and scopes, with every address resolved against the page
|
|
138
|
+
const mapImports = [];
|
|
139
|
+
const mapScopes = [];
|
|
140
|
+
const mapEntries = (value) => value && typeof value === "object"
|
|
141
|
+
? Object.entries(value).flatMap(([key, to]) => {
|
|
142
|
+
try {
|
|
143
|
+
return typeof to === "string" ? [[/^(\.{0,2}\/|[a-z][a-z0-9+.-]*:)/i.test(key) ? new URL(key, baseUrl).href : key, new URL(to, baseUrl).href]] : [];
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
: [];
|
|
150
|
+
const addImportMap = (json) => {
|
|
151
|
+
try {
|
|
152
|
+
const map = JSON.parse(json);
|
|
153
|
+
mapImports.push(...mapEntries(map.imports));
|
|
154
|
+
for (const [scope, entries] of Object.entries(map.scopes ?? {}))
|
|
155
|
+
mapScopes.push([new URL(scope, baseUrl).href, mapEntries(entries)]);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// an invalid import map is ignored, as a browser does
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const mapped = (entries, name) => {
|
|
162
|
+
let best;
|
|
163
|
+
for (const e of entries) {
|
|
164
|
+
if (e[0] === name)
|
|
165
|
+
return e[1];
|
|
166
|
+
if (e[0].endsWith("/") && name.startsWith(e[0]) && (!best || e[0].length > best[0].length))
|
|
167
|
+
best = e;
|
|
168
|
+
}
|
|
169
|
+
return best && best[1] + name.slice(best[0].length);
|
|
170
|
+
};
|
|
137
171
|
const resolveModule = (base, name) => {
|
|
172
|
+
if (mapImports.length || mapScopes.length) {
|
|
173
|
+
const scopes = mapScopes.filter(([s]) => s === base || (s.endsWith("/") && base.startsWith(s))).sort((a, b) => b[0].length - a[0].length);
|
|
174
|
+
for (const [, entries] of scopes) {
|
|
175
|
+
const hit = mapped(entries, name);
|
|
176
|
+
if (hit)
|
|
177
|
+
return hit;
|
|
178
|
+
}
|
|
179
|
+
const hit = mapped(mapImports, name);
|
|
180
|
+
if (hit)
|
|
181
|
+
return hit;
|
|
182
|
+
}
|
|
138
183
|
try {
|
|
139
184
|
return new URL(name, base).href;
|
|
140
185
|
}
|
|
@@ -240,9 +285,9 @@ export async function createDOM(source, options = {}) {
|
|
|
240
285
|
const code = source.replace(/\bimport\.meta\.url\b/g, JSON.stringify(name));
|
|
241
286
|
modules.set(name, code);
|
|
242
287
|
const pending = [];
|
|
243
|
-
for (const m of code.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*)(["'`])(
|
|
288
|
+
for (const m of code.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*)(["'`])([^"'`$\s]+)\1/g)) {
|
|
244
289
|
const target = resolveModule(name, m[2]);
|
|
245
|
-
if (!modules.has(target) && modules.size < MAX_MODULES) {
|
|
290
|
+
if (/^https?:/.test(target) && !modules.has(target) && modules.size < MAX_MODULES) {
|
|
246
291
|
modules.set(target, new Error(`could not load module ${target}`));
|
|
247
292
|
pending.push(target);
|
|
248
293
|
}
|
|
@@ -280,6 +325,10 @@ export async function createDOM(source, options = {}) {
|
|
|
280
325
|
.then((code) => ({ name, code }), () => undefined);
|
|
281
326
|
});
|
|
282
327
|
for (const [i, s] of list.entries()) {
|
|
328
|
+
if (s.importmap) {
|
|
329
|
+
addImportMap(s.text);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
283
332
|
let code = s.text;
|
|
284
333
|
let name = "inline";
|
|
285
334
|
if (s.src) {
|
package/dist/quickjs.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wasm-quickjs-dom",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "A small JSDOM for runtimes without Node or eval: QuickJS-ng (WebAssembly)
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A small JSDOM for runtimes without Node or eval: QuickJS-ng (WebAssembly) with a DOM written in C, for Cloudflare Workers, Vercel Edge, Deno, Bun and browsers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"jsdom",
|
|
23
23
|
"dom",
|
|
24
24
|
"quickjs",
|
|
25
|
-
"linkedom",
|
|
26
25
|
"wasm",
|
|
27
26
|
"webassembly",
|
|
28
27
|
"cloudflare-workers",
|
|
@@ -61,8 +60,10 @@
|
|
|
61
60
|
],
|
|
62
61
|
"license": "MIT",
|
|
63
62
|
"devDependencies": {
|
|
63
|
+
"@types/jsdom": "^30.0.0",
|
|
64
64
|
"@types/node": "26.6.5",
|
|
65
|
-
"
|
|
65
|
+
"htmlparser2": "10.1.0",
|
|
66
|
+
"jsdom": "^26.1.0",
|
|
66
67
|
"typescript": "^7.0.2",
|
|
67
68
|
"vitest": "^5.0.5",
|
|
68
69
|
"web-streams-polyfill": "4.3.0"
|