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/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 linkedom DOM.
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*)(["'`])(\.{0,2}\/[^"'`$]+|https?:[^"'`$]+)\1/g)) {
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",
4
- "description": "A small JSDOM for runtimes without Node or eval: QuickJS-ng (WebAssembly) on a linkedom DOM, for Cloudflare Workers, Vercel Edge, Deno, Bun and browsers",
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
- "linkedom": "0.18.13",
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"