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 +26 -4
- package/dist/plugin.cjs +52 -10
- package/dist/plugin.mjs +52 -10
- package/examples/build.mjs +20 -0
- package/examples/env.mjs +1 -0
- package/examples/package.json +5 -0
- package/package.json +10 -7
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 &&
|
|
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")))
|
|
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
|
|
252
|
-
|
|
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
|
-
|
|
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 &&
|
|
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")))
|
|
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
|
|
244
|
-
|
|
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");
|
package/examples/env.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const seed = () => Math.random() * Date.now()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unwasm",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
58
|
-
"@vitest/coverage-v8": "^1.1.
|
|
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.
|
|
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.
|
|
73
|
+
"vitest": "^1.1.1"
|
|
71
74
|
},
|
|
72
|
-
"packageManager": "pnpm@8.
|
|
75
|
+
"packageManager": "pnpm@8.13.1"
|
|
73
76
|
}
|