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 +26 -4
- package/dist/plugin.cjs +61 -21
- package/dist/plugin.mjs +61 -21
- 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
|
|
|
@@ -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
|
-
|
|
27
|
-
|
|
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 &&
|
|
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
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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 &&
|
|
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
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
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");
|
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.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.
|
|
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
|
}
|