unwasm 0.3.2 → 0.3.4

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 CHANGED
@@ -25,14 +25,15 @@ The development will be split into multiple stages.
25
25
  - [ ] ESM Loader ([unjs/unwasm#5](https://github.com/unjs/unwasm/issues/5))
26
26
  - [ ] Integration with [Wasmer](https://github.com/wasmerio) ([unjs/unwasm#6](https://github.com/unjs/unwasm/issues/6))
27
27
  - [ ] Convention for library authors exporting wasm modules ([unjs/unwasm#7](https://github.com/unjs/unwasm/issues/7))
28
+ - [x] Auto resolve imports from the imports map
28
29
 
29
30
  ## Bindings API
30
31
 
31
- When importing a `.wasm` module, unwasm resolves, reads, and then parses the module during build process to get the information about imports and exports and generate appropriate code bindings.
32
+ When importing a `.wasm` module, unwasm resolves, reads, and then parses the module during the build process to get the information about imports and exports and even tries to [automatically resolve imports](#auto-imports) and generate appropriate code bindings for the bundler.
32
33
 
33
- If the target environment supports [top level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) and also the wasm module requires no imports object (auto-detected after parsing), unwasm generates bindings to allow importing wasm module like any other ESM import.
34
+ If the target environment supports [top level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) and also the wasm module requires no imports object (or they are auto resolvable), unwasm generates bindings to allow importing wasm module like any other ESM import.
34
35
 
35
- If the target environment lacks support for top level `await` or the wasm module requires imports object or `lazy` plugin option is set to `true`, unwasm will export a wrapped [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object which can be called as a function to lazily evaluate the module with custom imports object. This way we still have a simple syntax as close as possible to ESM modules and also we can lazily initialize modules.
36
+ If the target environment lacks support for top-level `await` or the wasm module requires an imports object or `lazy` plugin option is set to `true`, unwasm will export a wrapped [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object which can be called as a function to evaluate the module with custom imports object lazily. This way we still have a simple syntax as close as possible to ESM modules and also we can lazily initialize modules.
36
37
 
37
38
  **Example:** Using static import
38
39
 
@@ -46,7 +47,7 @@ import { sum } from "unwasm/examples/sum.wasm";
46
47
  const { sum } = await import("unwasm/examples/sum.wasm");
47
48
  ```
48
49
 
49
- If your WebAssembly module requires an import object (which is likely!), the usage syntax would be slightly different as we need to initiate the module with an import object first.
50
+ If your WebAssembly module requires an import object (unwasm can [automatically infer them](#auto-imports)), the usage syntax would be slightly different as we need to initiate the module with an import object first.
50
51
 
51
52
  **Example:** Using dynamic import with imports object
52
53
 
@@ -169,6 +170,27 @@ Example parsed result:
169
170
  }
170
171
  ```
171
172
 
173
+ ## Auto Imports
174
+
175
+ unwasm can automatically infer the imports object and bundle them using imports maps (read more: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap), [Node.js](https://nodejs.org/api/packages.html#imports) and [WICG](https://github.com/WICG/import-maps)).
176
+
177
+ To hint to the bundler how to resolve imports needed by the `.wasm` file, you need to define them in a parent `package.json` file.
178
+
179
+ **Example:**
180
+
181
+ ```js
182
+ {
183
+ "exports": {
184
+ "./rand.wasm": "./rand.wasm"
185
+ },
186
+ "imports": {
187
+ "env": "./env.mjs"
188
+ }
189
+ }
190
+ ```
191
+
192
+ **Note:** The imports can also be prefixed with `#` like `#env` if you like to respect Node.js conventions.
193
+
172
194
  ## Development
173
195
 
174
196
  - Clone this repository
package/dist/plugin.cjs CHANGED
@@ -7,6 +7,7 @@ const pathe = require('pathe');
7
7
  const MagicString = require('magic-string');
8
8
  const unplugin$1 = require('unplugin');
9
9
  const node_crypto = require('node:crypto');
10
+ const pkgTypes = require('pkg-types');
10
11
  const tools = require('./tools.cjs');
11
12
  require('@webassemblyjs/wasm-parser');
12
13
 
@@ -15,27 +16,32 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
15
16
  const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString);
16
17
 
17
18
  const UNWASM_EXTERNAL_PREFIX = "\0unwasm:external:";
19
+ const UNWASM_EXTERNAL_RE = /(\0|\\0)unwasm:external:([^"']+)/gu;
18
20
  const UMWASM_HELPERS_ID = "\0unwasm:helpers";
19
21
  function sha1(source) {
20
22
  return node_crypto.createHash("sha1").update(source).digest("hex").slice(0, 16);
21
23
  }
22
24
 
23
25
  const js = String.raw;
24
- function getWasmBinding(asset, opts) {
26
+ async function getWasmBinding(asset, opts) {
27
+ const autoImports = await getWasmImports(asset);
25
28
  const envCode = opts.esmImport ? js`
26
- async function _instantiate(imports) {
29
+ ${autoImports.code};
30
+
31
+ async function _instantiate(imports = _imports) {
27
32
  const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
28
33
  return WebAssembly.instantiate(_mod, imports)
29
34
  }
30
35
  ` : js`
31
36
  import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
37
+ ${autoImports.code};
32
38
 
33
- function _instantiate(imports) {
39
+ function _instantiate(imports = _imports) {
34
40
  const _data = base64ToUint8Array("${asset.source.toString("base64")}")
35
41
  return WebAssembly.instantiate(_data, imports)
36
42
  }
37
43
  `;
38
- const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
44
+ const canTopAwait = opts.lazy !== true && autoImports.resolved;
39
45
  if (canTopAwait) {
40
46
  return js`
41
47
  import { getExports } from "${UMWASM_HELPERS_ID}";
@@ -133,6 +139,43 @@ export function createLazyWasmModule(_instantiator) {
133
139
  }
134
140
  `;
135
141
  }
142
+ async function getWasmImports(asset, opts) {
143
+ const importNames = Object.keys(asset.imports || {});
144
+ if (importNames.length === 0) {
145
+ return {
146
+ code: "const _imports = { /* no imports */ }",
147
+ resolved: true
148
+ };
149
+ }
150
+ const pkgJSON = await pkgTypes.readPackageJSON(asset.id);
151
+ let code = "const _imports = {";
152
+ let resolved = true;
153
+ for (const moduleName of importNames) {
154
+ const importNames2 = asset.imports[moduleName];
155
+ const pkgImport = pkgJSON.imports?.[moduleName] || pkgJSON.imports?.[`#${moduleName}`];
156
+ if (pkgImport) {
157
+ code = `import * as _imports_${moduleName} from "${pkgImport}";
158
+ ${code}`;
159
+ } else {
160
+ resolved = false;
161
+ }
162
+ code += `
163
+ ${moduleName}: {`;
164
+ for (const name of importNames2) {
165
+ code += pkgImport ? `
166
+ ${name}: _imports_${moduleName}.${name},
167
+ ` : `
168
+ ${name}: () => { throw new Error("\`${moduleName}.${name}\` is not provided!")},
169
+ `;
170
+ }
171
+ code += " },\n";
172
+ }
173
+ code += "};\n";
174
+ return {
175
+ code,
176
+ resolved
177
+ };
178
+ }
136
179
 
137
180
  const unplugin = unplugin$1.createUnplugin((opts) => {
138
181
  const assets = /* @__PURE__ */ Object.create(null);
@@ -206,7 +249,7 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
206
249
  const buff = await node_fs.promises.readFile(id);
207
250
  return buff.toString("binary");
208
251
  },
209
- transform(code, id) {
252
+ async transform(code, id) {
210
253
  if (!id.endsWith(".wasm")) {
211
254
  return;
212
255
  }
@@ -221,7 +264,7 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
221
264
  exports: parsed.exports
222
265
  };
223
266
  return {
224
- code: getWasmBinding(asset, opts),
267
+ code: await getWasmBinding(asset, opts),
225
268
  map: { mappings: "" }
226
269
  };
227
270
  },
@@ -229,7 +272,7 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
229
272
  if (!opts.esmImport) {
230
273
  return;
231
274
  }
232
- if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm"))) || !code.includes(UNWASM_EXTERNAL_PREFIX)) {
275
+ if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm")))) {
233
276
  return;
234
277
  }
235
278
  const s = new MagicString__default(code);
@@ -248,9 +291,8 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
248
291
  asset
249
292
  };
250
293
  };
251
- const ReplaceRE = new RegExp(`${UNWASM_EXTERNAL_PREFIX}([^"']+)`, "g");
252
- for (const match of code.matchAll(ReplaceRE)) {
253
- const resolved = resolveImport(match[1]);
294
+ for (const match of code.matchAll(UNWASM_EXTERNAL_RE)) {
295
+ const resolved = resolveImport(match[2]);
254
296
  const index = match.index;
255
297
  const len = match[0].length;
256
298
  if (!resolved || !index) {
package/dist/plugin.mjs CHANGED
@@ -3,31 +3,37 @@ import { basename } from 'pathe';
3
3
  import MagicString from 'magic-string';
4
4
  import { createUnplugin } from 'unplugin';
5
5
  import { createHash } from 'node:crypto';
6
+ import { readPackageJSON } from 'pkg-types';
6
7
  import { parseWasm } from './tools.mjs';
7
8
  import '@webassemblyjs/wasm-parser';
8
9
 
9
10
  const UNWASM_EXTERNAL_PREFIX = "\0unwasm:external:";
11
+ const UNWASM_EXTERNAL_RE = /(\0|\\0)unwasm:external:([^"']+)/gu;
10
12
  const UMWASM_HELPERS_ID = "\0unwasm:helpers";
11
13
  function sha1(source) {
12
14
  return createHash("sha1").update(source).digest("hex").slice(0, 16);
13
15
  }
14
16
 
15
17
  const js = String.raw;
16
- function getWasmBinding(asset, opts) {
18
+ async function getWasmBinding(asset, opts) {
19
+ const autoImports = await getWasmImports(asset);
17
20
  const envCode = opts.esmImport ? js`
18
- async function _instantiate(imports) {
21
+ ${autoImports.code};
22
+
23
+ async function _instantiate(imports = _imports) {
19
24
  const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
20
25
  return WebAssembly.instantiate(_mod, imports)
21
26
  }
22
27
  ` : js`
23
28
  import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
29
+ ${autoImports.code};
24
30
 
25
- function _instantiate(imports) {
31
+ function _instantiate(imports = _imports) {
26
32
  const _data = base64ToUint8Array("${asset.source.toString("base64")}")
27
33
  return WebAssembly.instantiate(_data, imports)
28
34
  }
29
35
  `;
30
- const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
36
+ const canTopAwait = opts.lazy !== true && autoImports.resolved;
31
37
  if (canTopAwait) {
32
38
  return js`
33
39
  import { getExports } from "${UMWASM_HELPERS_ID}";
@@ -125,6 +131,43 @@ export function createLazyWasmModule(_instantiator) {
125
131
  }
126
132
  `;
127
133
  }
134
+ async function getWasmImports(asset, opts) {
135
+ const importNames = Object.keys(asset.imports || {});
136
+ if (importNames.length === 0) {
137
+ return {
138
+ code: "const _imports = { /* no imports */ }",
139
+ resolved: true
140
+ };
141
+ }
142
+ const pkgJSON = await readPackageJSON(asset.id);
143
+ let code = "const _imports = {";
144
+ let resolved = true;
145
+ for (const moduleName of importNames) {
146
+ const importNames2 = asset.imports[moduleName];
147
+ const pkgImport = pkgJSON.imports?.[moduleName] || pkgJSON.imports?.[`#${moduleName}`];
148
+ if (pkgImport) {
149
+ code = `import * as _imports_${moduleName} from "${pkgImport}";
150
+ ${code}`;
151
+ } else {
152
+ resolved = false;
153
+ }
154
+ code += `
155
+ ${moduleName}: {`;
156
+ for (const name of importNames2) {
157
+ code += pkgImport ? `
158
+ ${name}: _imports_${moduleName}.${name},
159
+ ` : `
160
+ ${name}: () => { throw new Error("\`${moduleName}.${name}\` is not provided!")},
161
+ `;
162
+ }
163
+ code += " },\n";
164
+ }
165
+ code += "};\n";
166
+ return {
167
+ code,
168
+ resolved
169
+ };
170
+ }
128
171
 
129
172
  const unplugin = createUnplugin((opts) => {
130
173
  const assets = /* @__PURE__ */ Object.create(null);
@@ -198,7 +241,7 @@ const unplugin = createUnplugin((opts) => {
198
241
  const buff = await promises.readFile(id);
199
242
  return buff.toString("binary");
200
243
  },
201
- transform(code, id) {
244
+ async transform(code, id) {
202
245
  if (!id.endsWith(".wasm")) {
203
246
  return;
204
247
  }
@@ -213,7 +256,7 @@ const unplugin = createUnplugin((opts) => {
213
256
  exports: parsed.exports
214
257
  };
215
258
  return {
216
- code: getWasmBinding(asset, opts),
259
+ code: await getWasmBinding(asset, opts),
217
260
  map: { mappings: "" }
218
261
  };
219
262
  },
@@ -221,7 +264,7 @@ const unplugin = createUnplugin((opts) => {
221
264
  if (!opts.esmImport) {
222
265
  return;
223
266
  }
224
- if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm"))) || !code.includes(UNWASM_EXTERNAL_PREFIX)) {
267
+ if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm")))) {
225
268
  return;
226
269
  }
227
270
  const s = new MagicString(code);
@@ -240,9 +283,8 @@ const unplugin = createUnplugin((opts) => {
240
283
  asset
241
284
  };
242
285
  };
243
- const ReplaceRE = new RegExp(`${UNWASM_EXTERNAL_PREFIX}([^"']+)`, "g");
244
- for (const match of code.matchAll(ReplaceRE)) {
245
- const resolved = resolveImport(match[1]);
286
+ for (const match of code.matchAll(UNWASM_EXTERNAL_RE)) {
287
+ const resolved = resolveImport(match[2]);
246
288
  const index = match.index;
247
289
  const len = match[0].length;
248
290
  if (!resolved || !index) {
@@ -0,0 +1,20 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { main as asc } from "assemblyscript/asc";
3
+
4
+ async function compile(name) {
5
+ // https://www.assemblyscript.org/compiler.html#programmatic-usage
6
+ const res = await asc([`${name}.asc.ts`, "-o", `${name}.wasm`], {});
7
+
8
+ if (res.error) {
9
+ console.log(`Compilation failed for ${name}:`, res.error);
10
+ console.log(res.stderr.toString());
11
+ } else {
12
+ console.log(`Compiled: ${name}.wasm`);
13
+ console.log(res.stdout.toString());
14
+ }
15
+ }
16
+
17
+ process.chdir(fileURLToPath(new URL(".", import.meta.url)));
18
+
19
+ await compile("sum");
20
+ await compile("rand");
@@ -0,0 +1 @@
1
+ export const seed = () => Math.random() * Date.now()
@@ -0,0 +1,5 @@
1
+ {
2
+ "imports": {
3
+ "#env": "./env.mjs"
4
+ }
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unwasm",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "WebAssembly tools for JavaScript",
5
5
  "repository": "unjs/unwasm",
6
6
  "license": "MIT",
@@ -32,7 +32,9 @@
32
32
  "files": [
33
33
  "dist",
34
34
  "*.d.ts",
35
- "examples/*.wasm"
35
+ "examples/*.wasm",
36
+ "examples/package.json",
37
+ "examples/*.mjs"
36
38
  ],
37
39
  "scripts": {
38
40
  "build": "unbuild && pnpm build:examples",
@@ -50,12 +52,13 @@
50
52
  "magic-string": "^0.30.5",
51
53
  "mlly": "^1.4.2",
52
54
  "pathe": "^1.1.1",
55
+ "pkg-types": "^1.0.3",
53
56
  "unplugin": "^1.6.0"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@rollup/plugin-node-resolve": "^15.2.3",
57
- "@types/node": "^20.10.5",
58
- "@vitest/coverage-v8": "^1.1.0",
60
+ "@types/node": "^20.10.6",
61
+ "@vitest/coverage-v8": "^1.1.1",
59
62
  "assemblyscript": "^0.27.22",
60
63
  "changelogen": "^0.5.5",
61
64
  "eslint": "^8.56.0",
@@ -63,11 +66,11 @@
63
66
  "jiti": "^1.21.0",
64
67
  "miniflare": "^3.20231030.4",
65
68
  "prettier": "^3.1.1",
66
- "rollup": "^4.9.1",
69
+ "rollup": "^4.9.2",
67
70
  "typescript": "^5.3.3",
68
71
  "unbuild": "^2.0.0",
69
72
  "vite": "^5.0.10",
70
- "vitest": "^1.1.0"
73
+ "vitest": "^1.1.1"
71
74
  },
72
- "packageManager": "pnpm@8.12.1"
75
+ "packageManager": "pnpm@8.13.1"
73
76
  }