unwasm 0.3.1 → 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) {
27
- const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.id}").then(r => r.default || r);
28
+ ${autoImports.code};
29
+
30
+ async function _instantiate(imports = _imports) {
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);
@@ -202,28 +244,26 @@ const unplugin = unplugin$1.createUnplugin((opts) => {
202
244
  if (!id.endsWith(".wasm") || !node_fs.existsSync(id)) {
203
245
  return;
204
246
  }
205
- const source = await node_fs.promises.readFile(id);
206
- const name = `wasm/${pathe.basename(id, ".wasm")}-${sha1(source)}.wasm`;
207
- const parsed = parse(name, source);
208
- assets[id] = {
247
+ this.addWatchFile(id);
248
+ const buff = await node_fs.promises.readFile(id);
249
+ return buff.toString("binary");
250
+ },
251
+ async transform(code, id) {
252
+ if (!id.endsWith(".wasm")) {
253
+ return;
254
+ }
255
+ const buff = Buffer.from(code, "binary");
256
+ const name = `wasm/${pathe.basename(id, ".wasm")}-${sha1(buff)}.wasm`;
257
+ const parsed = parse(name, buff);
258
+ const asset = assets[name] = {
209
259
  name,
210
260
  id,
211
- source,
261
+ source: buff,
212
262
  imports: parsed.imports,
213
263
  exports: parsed.exports
214
264
  };
215
- return `export default "UNWASM DUMMY EXPORT";`;
216
- },
217
- transform(_code, id) {
218
- if (!id.endsWith(".wasm")) {
219
- return;
220
- }
221
- const asset = assets[id];
222
- if (!asset) {
223
- return;
224
- }
225
265
  return {
226
- code: getWasmBinding(asset, opts),
266
+ code: await getWasmBinding(asset, opts),
227
267
  map: { mappings: "" }
228
268
  };
229
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) {
19
- const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.id}").then(r => r.default || r);
20
+ ${autoImports.code};
21
+
22
+ async function _instantiate(imports = _imports) {
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);
@@ -194,28 +236,26 @@ const unplugin = createUnplugin((opts) => {
194
236
  if (!id.endsWith(".wasm") || !existsSync(id)) {
195
237
  return;
196
238
  }
197
- const source = await promises.readFile(id);
198
- const name = `wasm/${basename(id, ".wasm")}-${sha1(source)}.wasm`;
199
- const parsed = parse(name, source);
200
- assets[id] = {
239
+ this.addWatchFile(id);
240
+ const buff = await promises.readFile(id);
241
+ return buff.toString("binary");
242
+ },
243
+ async transform(code, id) {
244
+ if (!id.endsWith(".wasm")) {
245
+ return;
246
+ }
247
+ const buff = Buffer.from(code, "binary");
248
+ const name = `wasm/${basename(id, ".wasm")}-${sha1(buff)}.wasm`;
249
+ const parsed = parse(name, buff);
250
+ const asset = assets[name] = {
201
251
  name,
202
252
  id,
203
- source,
253
+ source: buff,
204
254
  imports: parsed.imports,
205
255
  exports: parsed.exports
206
256
  };
207
- return `export default "UNWASM DUMMY EXPORT";`;
208
- },
209
- transform(_code, id) {
210
- if (!id.endsWith(".wasm")) {
211
- return;
212
- }
213
- const asset = assets[id];
214
- if (!asset) {
215
- return;
216
- }
217
257
  return {
218
- code: getWasmBinding(asset, opts),
258
+ code: await getWasmBinding(asset, opts),
219
259
  map: { mappings: "" }
220
260
  };
221
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.1",
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
  }