unwasm 0.3.2 → 0.3.3

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
 
@@ -21,21 +22,25 @@ function sha1(source) {
21
22
  }
22
23
 
23
24
  const js = String.raw;
24
- function getWasmBinding(asset, opts) {
25
+ async function getWasmBinding(asset, opts) {
26
+ const autoImports = await getWasmImports(asset);
25
27
  const envCode = opts.esmImport ? js`
26
- async function _instantiate(imports) {
28
+ ${autoImports.code};
29
+
30
+ async function _instantiate(imports = _imports) {
27
31
  const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
28
32
  return WebAssembly.instantiate(_mod, imports)
29
33
  }
30
34
  ` : js`
31
35
  import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
36
+ ${autoImports.code};
32
37
 
33
- function _instantiate(imports) {
38
+ function _instantiate(imports = _imports) {
34
39
  const _data = base64ToUint8Array("${asset.source.toString("base64")}")
35
40
  return WebAssembly.instantiate(_data, imports)
36
41
  }
37
42
  `;
38
- const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
43
+ const canTopAwait = opts.lazy !== true && autoImports.resolved;
39
44
  if (canTopAwait) {
40
45
  return js`
41
46
  import { getExports } from "${UMWASM_HELPERS_ID}";
@@ -133,6 +138,43 @@ export function createLazyWasmModule(_instantiator) {
133
138
  }
134
139
  `;
135
140
  }
141
+ async function getWasmImports(asset, opts) {
142
+ const importNames = Object.keys(asset.imports || {});
143
+ if (importNames.length === 0) {
144
+ return {
145
+ code: "const _imports = { /* no imports */ }",
146
+ resolved: true
147
+ };
148
+ }
149
+ const pkgJSON = await pkgTypes.readPackageJSON(asset.id);
150
+ let code = "const _imports = {";
151
+ let resolved = true;
152
+ for (const moduleName of importNames) {
153
+ const importNames2 = asset.imports[moduleName];
154
+ const pkgImport = pkgJSON.imports?.[moduleName] || pkgJSON.imports?.[`#${moduleName}`];
155
+ if (pkgImport) {
156
+ code = `import * as _imports_${moduleName} from "${pkgImport}";
157
+ ${code}`;
158
+ } else {
159
+ resolved = false;
160
+ }
161
+ code += `
162
+ ${moduleName}: {`;
163
+ for (const name of importNames2) {
164
+ code += pkgImport ? `
165
+ ${name}: _imports_${moduleName}.${name},
166
+ ` : `
167
+ ${name}: () => { throw new Error("\`${moduleName}.${name}\` is not provided!")},
168
+ `;
169
+ }
170
+ code += " },\n";
171
+ }
172
+ code += "};\n";
173
+ return {
174
+ code,
175
+ resolved
176
+ };
177
+ }
136
178
 
137
179
  const unplugin = unplugin$1.createUnplugin((opts) => {
138
180
  const assets = /* @__PURE__ */ Object.create(null);
@@ -206,7 +248,7 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
206
248
  const buff = await node_fs.promises.readFile(id);
207
249
  return buff.toString("binary");
208
250
  },
209
- transform(code, id) {
251
+ async transform(code, id) {
210
252
  if (!id.endsWith(".wasm")) {
211
253
  return;
212
254
  }
@@ -221,7 +263,7 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
221
263
  exports: parsed.exports
222
264
  };
223
265
  return {
224
- code: getWasmBinding(asset, opts),
266
+ code: await getWasmBinding(asset, opts),
225
267
  map: { mappings: "" }
226
268
  };
227
269
  },
package/dist/plugin.mjs CHANGED
@@ -3,6 +3,7 @@ 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
 
@@ -13,21 +14,25 @@ function sha1(source) {
13
14
  }
14
15
 
15
16
  const js = String.raw;
16
- function getWasmBinding(asset, opts) {
17
+ async function getWasmBinding(asset, opts) {
18
+ const autoImports = await getWasmImports(asset);
17
19
  const envCode = opts.esmImport ? js`
18
- async function _instantiate(imports) {
20
+ ${autoImports.code};
21
+
22
+ async function _instantiate(imports = _imports) {
19
23
  const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
20
24
  return WebAssembly.instantiate(_mod, imports)
21
25
  }
22
26
  ` : js`
23
27
  import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
28
+ ${autoImports.code};
24
29
 
25
- function _instantiate(imports) {
30
+ function _instantiate(imports = _imports) {
26
31
  const _data = base64ToUint8Array("${asset.source.toString("base64")}")
27
32
  return WebAssembly.instantiate(_data, imports)
28
33
  }
29
34
  `;
30
- const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
35
+ const canTopAwait = opts.lazy !== true && autoImports.resolved;
31
36
  if (canTopAwait) {
32
37
  return js`
33
38
  import { getExports } from "${UMWASM_HELPERS_ID}";
@@ -125,6 +130,43 @@ export function createLazyWasmModule(_instantiator) {
125
130
  }
126
131
  `;
127
132
  }
133
+ async function getWasmImports(asset, opts) {
134
+ const importNames = Object.keys(asset.imports || {});
135
+ if (importNames.length === 0) {
136
+ return {
137
+ code: "const _imports = { /* no imports */ }",
138
+ resolved: true
139
+ };
140
+ }
141
+ const pkgJSON = await readPackageJSON(asset.id);
142
+ let code = "const _imports = {";
143
+ let resolved = true;
144
+ for (const moduleName of importNames) {
145
+ const importNames2 = asset.imports[moduleName];
146
+ const pkgImport = pkgJSON.imports?.[moduleName] || pkgJSON.imports?.[`#${moduleName}`];
147
+ if (pkgImport) {
148
+ code = `import * as _imports_${moduleName} from "${pkgImport}";
149
+ ${code}`;
150
+ } else {
151
+ resolved = false;
152
+ }
153
+ code += `
154
+ ${moduleName}: {`;
155
+ for (const name of importNames2) {
156
+ code += pkgImport ? `
157
+ ${name}: _imports_${moduleName}.${name},
158
+ ` : `
159
+ ${name}: () => { throw new Error("\`${moduleName}.${name}\` is not provided!")},
160
+ `;
161
+ }
162
+ code += " },\n";
163
+ }
164
+ code += "};\n";
165
+ return {
166
+ code,
167
+ resolved
168
+ };
169
+ }
128
170
 
129
171
  const unplugin = createUnplugin((opts) => {
130
172
  const assets = /* @__PURE__ */ Object.create(null);
@@ -198,7 +240,7 @@ const unplugin = createUnplugin((opts) => {
198
240
  const buff = await promises.readFile(id);
199
241
  return buff.toString("binary");
200
242
  },
201
- transform(code, id) {
243
+ async transform(code, id) {
202
244
  if (!id.endsWith(".wasm")) {
203
245
  return;
204
246
  }
@@ -213,7 +255,7 @@ const unplugin = createUnplugin((opts) => {
213
255
  exports: parsed.exports
214
256
  };
215
257
  return {
216
- code: getWasmBinding(asset, opts),
258
+ code: await getWasmBinding(asset, opts),
217
259
  map: { mappings: "" }
218
260
  };
219
261
  },
@@ -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.3",
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
  }