unwasm 0.3.0 → 0.3.2
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/dist/plugin.cjs +279 -0
- package/dist/plugin.d.cts +25 -0
- package/dist/plugin.d.mts +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.mjs +15 -21
- package/dist/tools.cjs +43 -0
- package/dist/tools.d.cts +25 -0
- package/package.json +17 -5
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const node_fs = require('node:fs');
|
|
6
|
+
const pathe = require('pathe');
|
|
7
|
+
const MagicString = require('magic-string');
|
|
8
|
+
const unplugin$1 = require('unplugin');
|
|
9
|
+
const node_crypto = require('node:crypto');
|
|
10
|
+
const tools = require('./tools.cjs');
|
|
11
|
+
require('@webassemblyjs/wasm-parser');
|
|
12
|
+
|
|
13
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
14
|
+
|
|
15
|
+
const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString);
|
|
16
|
+
|
|
17
|
+
const UNWASM_EXTERNAL_PREFIX = "\0unwasm:external:";
|
|
18
|
+
const UMWASM_HELPERS_ID = "\0unwasm:helpers";
|
|
19
|
+
function sha1(source) {
|
|
20
|
+
return node_crypto.createHash("sha1").update(source).digest("hex").slice(0, 16);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const js = String.raw;
|
|
24
|
+
function getWasmBinding(asset, opts) {
|
|
25
|
+
const envCode = opts.esmImport ? js`
|
|
26
|
+
async function _instantiate(imports) {
|
|
27
|
+
const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
|
|
28
|
+
return WebAssembly.instantiate(_mod, imports)
|
|
29
|
+
}
|
|
30
|
+
` : js`
|
|
31
|
+
import { base64ToUint8Array } from "${UMWASM_HELPERS_ID}";
|
|
32
|
+
|
|
33
|
+
function _instantiate(imports) {
|
|
34
|
+
const _data = base64ToUint8Array("${asset.source.toString("base64")}")
|
|
35
|
+
return WebAssembly.instantiate(_data, imports)
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
|
|
39
|
+
if (canTopAwait) {
|
|
40
|
+
return js`
|
|
41
|
+
import { getExports } from "${UMWASM_HELPERS_ID}";
|
|
42
|
+
${envCode}
|
|
43
|
+
|
|
44
|
+
const $exports = getExports(await _instantiate());
|
|
45
|
+
|
|
46
|
+
${asset.exports.map((name) => `export const ${name} = $exports.${name};`).join("\n")}
|
|
47
|
+
|
|
48
|
+
export default () => $exports;
|
|
49
|
+
`;
|
|
50
|
+
} else {
|
|
51
|
+
return js`
|
|
52
|
+
import { createLazyWasmModule } from "${UMWASM_HELPERS_ID}";
|
|
53
|
+
${envCode}
|
|
54
|
+
|
|
55
|
+
const _mod = createLazyWasmModule(_instantiate);
|
|
56
|
+
|
|
57
|
+
${asset.exports.map((name) => `export const ${name} = _mod.${name};`).join("\n")}
|
|
58
|
+
|
|
59
|
+
export default _mod;
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function getPluginUtils() {
|
|
64
|
+
return js`
|
|
65
|
+
export function debug(...args) {
|
|
66
|
+
console.log('[wasm] [debug]', ...args);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getExports(input) {
|
|
70
|
+
return input?.instance?.exports || input?.exports || input;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function base64ToUint8Array(str) {
|
|
74
|
+
const data = atob(str);
|
|
75
|
+
const size = data.length;
|
|
76
|
+
const bytes = new Uint8Array(size);
|
|
77
|
+
for (let i = 0; i < size; i++) {
|
|
78
|
+
bytes[i] = data.charCodeAt(i);
|
|
79
|
+
}
|
|
80
|
+
return bytes;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createLazyWasmModule(_instantiator) {
|
|
84
|
+
const _exports = Object.create(null);
|
|
85
|
+
let _loaded;
|
|
86
|
+
let _promise;
|
|
87
|
+
|
|
88
|
+
const init = (imports) => {
|
|
89
|
+
if (_loaded) {
|
|
90
|
+
return Promise.resolve(exportsProxy);
|
|
91
|
+
}
|
|
92
|
+
if (_promise) {
|
|
93
|
+
return _promise;
|
|
94
|
+
}
|
|
95
|
+
return _promise = _instantiator(imports)
|
|
96
|
+
.then(r => {
|
|
97
|
+
Object.assign(_exports, getExports(r));
|
|
98
|
+
_loaded = true;
|
|
99
|
+
_promise = undefined;
|
|
100
|
+
return exportsProxy;
|
|
101
|
+
})
|
|
102
|
+
.catch(error => {
|
|
103
|
+
_promise = undefined;
|
|
104
|
+
console.error('[wasm] [error]', error);
|
|
105
|
+
throw error;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const exportsProxy = new Proxy(_exports, {
|
|
110
|
+
get(_, prop) {
|
|
111
|
+
if (_loaded) {
|
|
112
|
+
return _exports[prop];
|
|
113
|
+
}
|
|
114
|
+
return (...args) => {
|
|
115
|
+
return _loaded
|
|
116
|
+
? _exports[prop]?.(...args)
|
|
117
|
+
: init().then(() => _exports[prop]?.(...args));
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const lazyProxy = new Proxy(() => {}, {
|
|
124
|
+
get(_, prop) {
|
|
125
|
+
return exportsProxy[prop];
|
|
126
|
+
},
|
|
127
|
+
apply(_, __, args) {
|
|
128
|
+
return init(args[0])
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return lazyProxy;
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const unplugin = unplugin$1.createUnplugin((opts) => {
|
|
138
|
+
const assets = /* @__PURE__ */ Object.create(null);
|
|
139
|
+
const _parseCache = /* @__PURE__ */ Object.create(null);
|
|
140
|
+
function parse(name, source) {
|
|
141
|
+
if (_parseCache[name]) {
|
|
142
|
+
return _parseCache[name];
|
|
143
|
+
}
|
|
144
|
+
const parsed = tools.parseWasm(source);
|
|
145
|
+
const imports = /* @__PURE__ */ Object.create(null);
|
|
146
|
+
const exports = [];
|
|
147
|
+
for (const mod of parsed.modules) {
|
|
148
|
+
exports.push(...mod.exports.map((e) => e.name));
|
|
149
|
+
for (const imp of mod.imports) {
|
|
150
|
+
if (!imports[imp.module]) {
|
|
151
|
+
imports[imp.module] = [];
|
|
152
|
+
}
|
|
153
|
+
imports[imp.module].push(imp.name);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
_parseCache[name] = {
|
|
157
|
+
imports,
|
|
158
|
+
exports
|
|
159
|
+
};
|
|
160
|
+
return _parseCache[name];
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
name: "unwasm",
|
|
164
|
+
rollup: {
|
|
165
|
+
async resolveId(id, importer) {
|
|
166
|
+
if (id === UMWASM_HELPERS_ID) {
|
|
167
|
+
return id;
|
|
168
|
+
}
|
|
169
|
+
if (id.startsWith(UNWASM_EXTERNAL_PREFIX)) {
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
external: true
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (id.endsWith(".wasm")) {
|
|
176
|
+
const r = await this.resolve(id, importer, { skipSelf: true });
|
|
177
|
+
if (r?.id && r.id !== id) {
|
|
178
|
+
return {
|
|
179
|
+
id: r.id.startsWith("file://") ? r.id.slice(7) : r.id,
|
|
180
|
+
external: false,
|
|
181
|
+
moduleSideEffects: false
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
generateBundle() {
|
|
187
|
+
if (opts.esmImport) {
|
|
188
|
+
for (const asset of Object.values(assets)) {
|
|
189
|
+
this.emitFile({
|
|
190
|
+
type: "asset",
|
|
191
|
+
source: asset.source,
|
|
192
|
+
fileName: asset.name
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
async load(id) {
|
|
199
|
+
if (id === UMWASM_HELPERS_ID) {
|
|
200
|
+
return getPluginUtils();
|
|
201
|
+
}
|
|
202
|
+
if (!id.endsWith(".wasm") || !node_fs.existsSync(id)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.addWatchFile(id);
|
|
206
|
+
const buff = await node_fs.promises.readFile(id);
|
|
207
|
+
return buff.toString("binary");
|
|
208
|
+
},
|
|
209
|
+
transform(code, id) {
|
|
210
|
+
if (!id.endsWith(".wasm")) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const buff = Buffer.from(code, "binary");
|
|
214
|
+
const name = `wasm/${pathe.basename(id, ".wasm")}-${sha1(buff)}.wasm`;
|
|
215
|
+
const parsed = parse(name, buff);
|
|
216
|
+
const asset = assets[name] = {
|
|
217
|
+
name,
|
|
218
|
+
id,
|
|
219
|
+
source: buff,
|
|
220
|
+
imports: parsed.imports,
|
|
221
|
+
exports: parsed.exports
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
code: getWasmBinding(asset, opts),
|
|
225
|
+
map: { mappings: "" }
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
renderChunk(code, chunk) {
|
|
229
|
+
if (!opts.esmImport) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm"))) || !code.includes(UNWASM_EXTERNAL_PREFIX)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const s = new MagicString__default(code);
|
|
236
|
+
const resolveImport = (id) => {
|
|
237
|
+
if (typeof id !== "string") {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const asset = assets[id];
|
|
241
|
+
if (!asset) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const nestedLevel = chunk.fileName.split("/").length - 1;
|
|
245
|
+
const relativeId = (nestedLevel ? "../".repeat(nestedLevel) : "./") + asset.name;
|
|
246
|
+
return {
|
|
247
|
+
relativeId,
|
|
248
|
+
asset
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
const ReplaceRE = new RegExp(`${UNWASM_EXTERNAL_PREFIX}([^"']+)`, "g");
|
|
252
|
+
for (const match of code.matchAll(ReplaceRE)) {
|
|
253
|
+
const resolved = resolveImport(match[1]);
|
|
254
|
+
const index = match.index;
|
|
255
|
+
const len = match[0].length;
|
|
256
|
+
if (!resolved || !index) {
|
|
257
|
+
console.warn(
|
|
258
|
+
`Failed to resolve WASM import: ${JSON.stringify(match[1])}`
|
|
259
|
+
);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
s.overwrite(index, index + len, resolved.relativeId);
|
|
263
|
+
}
|
|
264
|
+
if (s.hasChanged()) {
|
|
265
|
+
return {
|
|
266
|
+
code: s.toString(),
|
|
267
|
+
map: s.generateMap({ includeContent: true })
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
const rollup = unplugin.rollup;
|
|
274
|
+
const index = {
|
|
275
|
+
rollup
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
exports.default = index;
|
|
279
|
+
exports.rollup = rollup;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Plugin } from 'rollup';
|
|
2
|
+
|
|
3
|
+
interface UnwasmPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Directly import the `.wasm` files instead of bundling as base64 string.
|
|
6
|
+
*
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
esmImport?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Avoid using top level await and always use a proxy.
|
|
12
|
+
*
|
|
13
|
+
* Useful for compatibility with environments that don't support top level await.
|
|
14
|
+
*
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
lazy?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare const rollup: (opts: UnwasmPluginOptions) => Plugin;
|
|
21
|
+
declare const _default: {
|
|
22
|
+
rollup: (opts: UnwasmPluginOptions) => Plugin<any>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { type UnwasmPluginOptions, _default as default, rollup };
|
package/dist/plugin.d.mts
CHANGED
package/dist/plugin.d.ts
CHANGED
package/dist/plugin.mjs
CHANGED
|
@@ -16,7 +16,7 @@ const js = String.raw;
|
|
|
16
16
|
function getWasmBinding(asset, opts) {
|
|
17
17
|
const envCode = opts.esmImport ? js`
|
|
18
18
|
async function _instantiate(imports) {
|
|
19
|
-
const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.
|
|
19
|
+
const _mod = await import("${UNWASM_EXTERNAL_PREFIX}${asset.name}").then(r => r.default || r);
|
|
20
20
|
return WebAssembly.instantiate(_mod, imports)
|
|
21
21
|
}
|
|
22
22
|
` : js`
|
|
@@ -37,9 +37,7 @@ const $exports = getExports(await _instantiate());
|
|
|
37
37
|
|
|
38
38
|
${asset.exports.map((name) => `export const ${name} = $exports.${name};`).join("\n")}
|
|
39
39
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
export default $exports;
|
|
40
|
+
export default () => $exports;
|
|
43
41
|
`;
|
|
44
42
|
} else {
|
|
45
43
|
return js`
|
|
@@ -50,8 +48,6 @@ const _mod = createLazyWasmModule(_instantiate);
|
|
|
50
48
|
|
|
51
49
|
${asset.exports.map((name) => `export const ${name} = _mod.${name};`).join("\n")}
|
|
52
50
|
|
|
53
|
-
export const $init = _mod.$init.bind(_mod);
|
|
54
|
-
|
|
55
51
|
export default _mod;
|
|
56
52
|
`;
|
|
57
53
|
}
|
|
@@ -198,26 +194,24 @@ const unplugin = createUnplugin((opts) => {
|
|
|
198
194
|
if (!id.endsWith(".wasm") || !existsSync(id)) {
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
this.addWatchFile(id);
|
|
198
|
+
const buff = await promises.readFile(id);
|
|
199
|
+
return buff.toString("binary");
|
|
200
|
+
},
|
|
201
|
+
transform(code, id) {
|
|
202
|
+
if (!id.endsWith(".wasm")) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const buff = Buffer.from(code, "binary");
|
|
206
|
+
const name = `wasm/${basename(id, ".wasm")}-${sha1(buff)}.wasm`;
|
|
207
|
+
const parsed = parse(name, buff);
|
|
208
|
+
const asset = assets[name] = {
|
|
205
209
|
name,
|
|
206
210
|
id,
|
|
207
|
-
source,
|
|
211
|
+
source: buff,
|
|
208
212
|
imports: parsed.imports,
|
|
209
213
|
exports: parsed.exports
|
|
210
214
|
};
|
|
211
|
-
return `export default "UNWASM DUMMY EXPORT";`;
|
|
212
|
-
},
|
|
213
|
-
transform(_code, id) {
|
|
214
|
-
if (!id.endsWith(".wasm")) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const asset = assets[id];
|
|
218
|
-
if (!asset) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
215
|
return {
|
|
222
216
|
code: getWasmBinding(asset, opts),
|
|
223
217
|
map: { mappings: "" }
|
package/dist/tools.cjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const wasmParser = require('@webassemblyjs/wasm-parser');
|
|
4
|
+
|
|
5
|
+
function parseWasm(source) {
|
|
6
|
+
const ast = wasmParser.decode(source);
|
|
7
|
+
const modules = [];
|
|
8
|
+
for (const body of ast.body) {
|
|
9
|
+
if (body.type === "Module") {
|
|
10
|
+
const module = {
|
|
11
|
+
imports: [],
|
|
12
|
+
exports: []
|
|
13
|
+
};
|
|
14
|
+
modules.push(module);
|
|
15
|
+
for (const field of body.fields) {
|
|
16
|
+
if (field.type === "ModuleImport") {
|
|
17
|
+
module.imports.push({
|
|
18
|
+
module: field.module,
|
|
19
|
+
name: field.name,
|
|
20
|
+
returnType: field.descr?.signature?.results?.[0],
|
|
21
|
+
params: field.descr.signature.params?.map(
|
|
22
|
+
(p) => ({
|
|
23
|
+
id: p.id,
|
|
24
|
+
type: p.valtype
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
});
|
|
28
|
+
} else if (field.type === "ModuleExport") {
|
|
29
|
+
module.exports.push({
|
|
30
|
+
name: field.name,
|
|
31
|
+
id: field.descr.id.value,
|
|
32
|
+
type: field.descr.exportType
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
modules
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exports.parseWasm = parseWasm;
|
package/dist/tools.d.cts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type ParsedWasmModule = {
|
|
2
|
+
id?: string;
|
|
3
|
+
imports: ModuleImport[];
|
|
4
|
+
exports: ModuleExport[];
|
|
5
|
+
};
|
|
6
|
+
type ModuleImport = {
|
|
7
|
+
module: string;
|
|
8
|
+
name: string;
|
|
9
|
+
returnType?: string;
|
|
10
|
+
params?: {
|
|
11
|
+
id?: string;
|
|
12
|
+
type: string;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
type ModuleExport = {
|
|
16
|
+
name: string;
|
|
17
|
+
id: string | number;
|
|
18
|
+
type: "Func" | "Memory";
|
|
19
|
+
};
|
|
20
|
+
type ParseResult = {
|
|
21
|
+
modules: ParsedWasmModule[];
|
|
22
|
+
};
|
|
23
|
+
declare function parseWasm(source: Buffer | ArrayBuffer): ParseResult;
|
|
24
|
+
|
|
25
|
+
export { type ModuleExport, type ModuleImport, type ParseResult, type ParsedWasmModule, parseWasm };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unwasm",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "WebAssembly tools for JavaScript",
|
|
5
5
|
"repository": "unjs/unwasm",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,12 +9,24 @@
|
|
|
9
9
|
"exports": {
|
|
10
10
|
"./examples/*": "./examples/*",
|
|
11
11
|
"./plugin": {
|
|
12
|
-
"
|
|
13
|
-
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/plugin.d.mts",
|
|
14
|
+
"default": "./dist/plugin.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/plugin.d.ts",
|
|
18
|
+
"default": "./dist/plugin.cjs"
|
|
19
|
+
}
|
|
14
20
|
},
|
|
15
21
|
"./tools": {
|
|
16
|
-
"
|
|
17
|
-
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/tools.d.mts",
|
|
24
|
+
"default": "./dist/tools.mjs"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/tools.d.ts",
|
|
28
|
+
"default": "./dist/tools.cjs"
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
31
|
},
|
|
20
32
|
"files": [
|