unwasm 0.2.0 → 0.3.1
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 +86 -38
- package/dist/plugin.cjs +281 -0
- package/dist/plugin.d.cts +25 -0
- package/dist/plugin.d.mts +25 -0
- package/dist/plugin.d.ts +25 -0
- package/dist/{plugin/index.mjs → plugin.mjs} +72 -33
- package/dist/tools.cjs +43 -0
- package/dist/tools.d.cts +25 -0
- package/dist/tools.d.mts +25 -0
- package/dist/tools.d.ts +25 -0
- package/dist/tools.mjs +41 -0
- package/package.json +20 -3
- package/plugin.d.ts +2 -0
- package/dist/plugin/index.d.mts +0 -22
- package/dist/plugin/index.d.ts +0 -22
package/README.md
CHANGED
|
@@ -17,53 +17,42 @@ The development will be split into multiple stages.
|
|
|
17
17
|
> [!IMPORTANT]
|
|
18
18
|
> This Project is under development! See the linked discussions to be involved!
|
|
19
19
|
|
|
20
|
-
- [ ]
|
|
20
|
+
- [ ] Builder plugin powered by [unjs/unplugin](https://github.com/unjs/unplugin) ([unjs/unwasm#2](https://github.com/unjs/unwasm/issues/2))
|
|
21
21
|
- [x] Rollup
|
|
22
|
-
- [ ] Tools
|
|
23
|
-
- [
|
|
24
|
-
- [ ]
|
|
22
|
+
- [ ] Build Tools ([unjs/unwasm#3](https://github.com/unjs/unwasm/issues/3))
|
|
23
|
+
- [x] `parseWasm`
|
|
24
|
+
- [ ] Runtime Utils ([unjs/unwasm#4](https://github.com/unjs/unwasm/issues/4))
|
|
25
|
+
- [ ] ESM Loader ([unjs/unwasm#5](https://github.com/unjs/unwasm/issues/5))
|
|
25
26
|
- [ ] Integration with [Wasmer](https://github.com/wasmerio) ([unjs/unwasm#6](https://github.com/unjs/unwasm/issues/6))
|
|
26
|
-
- [ ] Convention
|
|
27
|
+
- [ ] Convention for library authors exporting wasm modules ([unjs/unwasm#7](https://github.com/unjs/unwasm/issues/7))
|
|
27
28
|
|
|
28
29
|
## Bindings API
|
|
29
30
|
|
|
30
|
-
When importing a `.wasm` module
|
|
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.
|
|
31
32
|
|
|
32
|
-
|
|
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.
|
|
33
34
|
|
|
34
|
-
|
|
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
|
+
|
|
37
|
+
**Example:** Using static import
|
|
35
38
|
|
|
36
39
|
```js
|
|
37
40
|
import { sum } from "unwasm/examples/sum.wasm";
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
**Using dynamic import
|
|
43
|
+
**Example:** Using dynamic import
|
|
41
44
|
|
|
42
45
|
```js
|
|
43
|
-
const { sum } = await import("unwasm/examples/sum.wasm")
|
|
44
|
-
(mod) => mod.default,
|
|
45
|
-
);
|
|
46
|
+
const { sum } = await import("unwasm/examples/sum.wasm");
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
**Using static import with imports object:**
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
import { rand, $init } from "unwasm/examples/rand.wasm";
|
|
54
|
-
|
|
55
|
-
await $init({
|
|
56
|
-
env: {
|
|
57
|
-
seed: () => () => Math.random() * Date.now(),
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
```
|
|
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.
|
|
61
50
|
|
|
62
|
-
**Using dynamic import with imports object
|
|
51
|
+
**Example:** Using dynamic import with imports object
|
|
63
52
|
|
|
64
53
|
```js
|
|
65
|
-
const { rand } = await import("unwasm/examples/rand.wasm").then((
|
|
66
|
-
|
|
54
|
+
const { rand } = await import("unwasm/examples/rand.wasm").then((r) =>
|
|
55
|
+
r.default({
|
|
67
56
|
env: {
|
|
68
57
|
seed: () => () => Math.random() * Date.now(),
|
|
69
58
|
},
|
|
@@ -71,19 +60,28 @@ const { rand } = await import("unwasm/examples/rand.wasm").then((mod) =>
|
|
|
71
60
|
);
|
|
72
61
|
```
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
**Example:** Using static import with imports object
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import initRand, { rand } from "unwasm/examples/rand.wasm";
|
|
67
|
+
|
|
68
|
+
await initRand({
|
|
69
|
+
env: {
|
|
70
|
+
seed: () => () => Math.random() * Date.now(),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
76
74
|
|
|
77
75
|
> [!NOTE]
|
|
78
|
-
>
|
|
76
|
+
> When using **static import syntax**, and before initializing the module, the named exports will be wrapped into a function by proxy that waits for the module initialization and if called before init, will immediately try to call init without imports and return a Promise that calls a function after init.
|
|
79
77
|
|
|
80
|
-
##
|
|
78
|
+
## Integration
|
|
81
79
|
|
|
82
|
-
Unwasm needs to transform the `.wasm` imports to the compatible bindings. Currently only method is using a rollup plugin. In the future, more usage methods will be introduced.
|
|
80
|
+
Unwasm needs to transform the `.wasm` imports to the compatible bindings. Currently, the only method is using a rollup plugin. In the future, more usage methods will be introduced.
|
|
83
81
|
|
|
84
82
|
### Install
|
|
85
83
|
|
|
86
|
-
First, install the [`unwasm`
|
|
84
|
+
First, install the [`unwasm`](https://www.npmjs.com/package/unwasm) npm package.
|
|
87
85
|
|
|
88
86
|
```sh
|
|
89
87
|
# npm
|
|
@@ -105,11 +103,11 @@ bun i -D unwasm
|
|
|
105
103
|
|
|
106
104
|
```js
|
|
107
105
|
// rollup.config.js
|
|
108
|
-
import
|
|
106
|
+
import { rollup as unwasm } from "unwasm/plugin";
|
|
109
107
|
|
|
110
108
|
export default {
|
|
111
109
|
plugins: [
|
|
112
|
-
|
|
110
|
+
unwasm({
|
|
113
111
|
/* options */
|
|
114
112
|
}),
|
|
115
113
|
],
|
|
@@ -118,8 +116,58 @@ export default {
|
|
|
118
116
|
|
|
119
117
|
### Plugin Options
|
|
120
118
|
|
|
121
|
-
- `esmImport`: Direct import the wasm file instead of bundling, required in Cloudflare Workers (default is `false`)
|
|
122
|
-
- `lazy`: Import `.wasm` files using a lazily evaluated
|
|
119
|
+
- `esmImport`: Direct import the wasm file instead of bundling, required in Cloudflare Workers and works with environments that allow natively importing a `.wasm` module (default is `false`)
|
|
120
|
+
- `lazy`: Import `.wasm` files using a lazily evaluated proxy for compatibility with runtimes without top-level await support (default is `false`)
|
|
121
|
+
|
|
122
|
+
## Tools
|
|
123
|
+
|
|
124
|
+
unwasm provides useful build tools to operate on `.wasm` modules directly.
|
|
125
|
+
|
|
126
|
+
**Note:** `unwasm/tools` subpath export is **not** meant or optimized for production runtime. Only rely on it for development and build time.
|
|
127
|
+
|
|
128
|
+
### `parseWasm`
|
|
129
|
+
|
|
130
|
+
Parses `wasm` binary format with useful information using [webassemblyjs/wasm-parser](https://github.com/xtuc/webassemblyjs/tree/master/packages/wasm-parser).
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import { readFile } from "node:fs/promises";
|
|
134
|
+
import { parseWasm } from "unwasm/tools";
|
|
135
|
+
|
|
136
|
+
const source = await readFile(new URL("./examples/sum.wasm", import.meta.url));
|
|
137
|
+
const parsed = parseWasm(source);
|
|
138
|
+
console.log(JSON.stringify(parsed, undefined, 2));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Example parsed result:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"modules": [
|
|
146
|
+
{
|
|
147
|
+
"exports": [
|
|
148
|
+
{
|
|
149
|
+
"id": 5,
|
|
150
|
+
"name": "rand",
|
|
151
|
+
"type": "Func"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": 0,
|
|
155
|
+
"name": "memory",
|
|
156
|
+
"type": "Memory"
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
"imports": [
|
|
160
|
+
{
|
|
161
|
+
"module": "env",
|
|
162
|
+
"name": "seed",
|
|
163
|
+
"params": [],
|
|
164
|
+
"returnType": "f64"
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
123
171
|
|
|
124
172
|
## Development
|
|
125
173
|
|
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
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.id}").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
|
+
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] = {
|
|
209
|
+
name,
|
|
210
|
+
id,
|
|
211
|
+
source,
|
|
212
|
+
imports: parsed.imports,
|
|
213
|
+
exports: parsed.exports
|
|
214
|
+
};
|
|
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
|
+
return {
|
|
226
|
+
code: getWasmBinding(asset, opts),
|
|
227
|
+
map: { mappings: "" }
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
renderChunk(code, chunk) {
|
|
231
|
+
if (!opts.esmImport) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!(chunk.moduleIds.some((id) => id.endsWith(".wasm")) || chunk.imports.some((id) => id.endsWith(".wasm"))) || !code.includes(UNWASM_EXTERNAL_PREFIX)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const s = new MagicString__default(code);
|
|
238
|
+
const resolveImport = (id) => {
|
|
239
|
+
if (typeof id !== "string") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const asset = assets[id];
|
|
243
|
+
if (!asset) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const nestedLevel = chunk.fileName.split("/").length - 1;
|
|
247
|
+
const relativeId = (nestedLevel ? "../".repeat(nestedLevel) : "./") + asset.name;
|
|
248
|
+
return {
|
|
249
|
+
relativeId,
|
|
250
|
+
asset
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
const ReplaceRE = new RegExp(`${UNWASM_EXTERNAL_PREFIX}([^"']+)`, "g");
|
|
254
|
+
for (const match of code.matchAll(ReplaceRE)) {
|
|
255
|
+
const resolved = resolveImport(match[1]);
|
|
256
|
+
const index = match.index;
|
|
257
|
+
const len = match[0].length;
|
|
258
|
+
if (!resolved || !index) {
|
|
259
|
+
console.warn(
|
|
260
|
+
`Failed to resolve WASM import: ${JSON.stringify(match[1])}`
|
|
261
|
+
);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
s.overwrite(index, index + len, resolved.relativeId);
|
|
265
|
+
}
|
|
266
|
+
if (s.hasChanged()) {
|
|
267
|
+
return {
|
|
268
|
+
code: s.toString(),
|
|
269
|
+
map: s.generateMap({ includeContent: true })
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
const rollup = unplugin.rollup;
|
|
276
|
+
const index = {
|
|
277
|
+
rollup
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
exports.default = index;
|
|
281
|
+
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 };
|
|
@@ -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.ts
ADDED
|
@@ -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 };
|
|
@@ -3,6 +3,8 @@ 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 { parseWasm } from './tools.mjs';
|
|
7
|
+
import '@webassemblyjs/wasm-parser';
|
|
6
8
|
|
|
7
9
|
const UNWASM_EXTERNAL_PREFIX = "\0unwasm:external:";
|
|
8
10
|
const UMWASM_HELPERS_ID = "\0unwasm:helpers";
|
|
@@ -25,24 +27,38 @@ function _instantiate(imports) {
|
|
|
25
27
|
return WebAssembly.instantiate(_data, imports)
|
|
26
28
|
}
|
|
27
29
|
`;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
const canTopAwait = opts.lazy !== true && Object.keys(asset.imports).length === 0;
|
|
31
|
+
if (canTopAwait) {
|
|
32
|
+
return js`
|
|
33
|
+
import { getExports } from "${UMWASM_HELPERS_ID}";
|
|
34
|
+
${envCode}
|
|
35
|
+
|
|
36
|
+
const $exports = getExports(await _instantiate());
|
|
37
|
+
|
|
38
|
+
${asset.exports.map((name) => `export const ${name} = $exports.${name};`).join("\n")}
|
|
39
|
+
|
|
40
|
+
export default () => $exports;
|
|
41
|
+
`;
|
|
42
|
+
} else {
|
|
43
|
+
return js`
|
|
44
|
+
import { createLazyWasmModule } from "${UMWASM_HELPERS_ID}";
|
|
30
45
|
${envCode}
|
|
31
|
-
const _mod = createUnwasmModule(_instantiate);
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
const _mod = createLazyWasmModule(_instantiate);
|
|
48
|
+
|
|
49
|
+
${asset.exports.map((name) => `export const ${name} = _mod.${name};`).join("\n")}
|
|
35
50
|
|
|
36
51
|
export default _mod;
|
|
37
|
-
`;
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
55
|
function getPluginUtils() {
|
|
40
56
|
return js`
|
|
41
57
|
export function debug(...args) {
|
|
42
|
-
console.log('[wasm]', ...args);
|
|
58
|
+
console.log('[wasm] [debug]', ...args);
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
function getExports(input) {
|
|
61
|
+
export function getExports(input) {
|
|
46
62
|
return input?.instance?.exports || input?.exports || input;
|
|
47
63
|
}
|
|
48
64
|
|
|
@@ -56,14 +72,14 @@ export function base64ToUint8Array(str) {
|
|
|
56
72
|
return bytes;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
export function
|
|
75
|
+
export function createLazyWasmModule(_instantiator) {
|
|
60
76
|
const _exports = Object.create(null);
|
|
61
77
|
let _loaded;
|
|
62
78
|
let _promise;
|
|
63
79
|
|
|
64
|
-
const
|
|
80
|
+
const init = (imports) => {
|
|
65
81
|
if (_loaded) {
|
|
66
|
-
return Promise.resolve(
|
|
82
|
+
return Promise.resolve(exportsProxy);
|
|
67
83
|
}
|
|
68
84
|
if (_promise) {
|
|
69
85
|
return _promise;
|
|
@@ -73,16 +89,16 @@ export function createUnwasmModule(_instantiator) {
|
|
|
73
89
|
Object.assign(_exports, getExports(r));
|
|
74
90
|
_loaded = true;
|
|
75
91
|
_promise = undefined;
|
|
76
|
-
return
|
|
92
|
+
return exportsProxy;
|
|
77
93
|
})
|
|
78
94
|
.catch(error => {
|
|
79
95
|
_promise = undefined;
|
|
80
|
-
console.error('[wasm]', error);
|
|
96
|
+
console.error('[wasm] [error]', error);
|
|
81
97
|
throw error;
|
|
82
98
|
});
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
const
|
|
101
|
+
const exportsProxy = new Proxy(_exports, {
|
|
86
102
|
get(_, prop) {
|
|
87
103
|
if (_loaded) {
|
|
88
104
|
return _exports[prop];
|
|
@@ -90,33 +106,51 @@ export function createUnwasmModule(_instantiator) {
|
|
|
90
106
|
return (...args) => {
|
|
91
107
|
return _loaded
|
|
92
108
|
? _exports[prop]?.(...args)
|
|
93
|
-
:
|
|
109
|
+
: init().then(() => _exports[prop]?.(...args));
|
|
94
110
|
};
|
|
95
111
|
},
|
|
96
112
|
});
|
|
97
113
|
|
|
98
|
-
const _instance = {
|
|
99
|
-
$init,
|
|
100
|
-
$exports,
|
|
101
|
-
};
|
|
102
114
|
|
|
103
|
-
const
|
|
115
|
+
const lazyProxy = new Proxy(() => {}, {
|
|
104
116
|
get(_, prop) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
117
|
+
return exportsProxy[prop];
|
|
118
|
+
},
|
|
119
|
+
apply(_, __, args) {
|
|
120
|
+
return init(args[0])
|
|
121
|
+
},
|
|
111
122
|
});
|
|
112
123
|
|
|
113
|
-
return
|
|
124
|
+
return lazyProxy;
|
|
114
125
|
}
|
|
115
126
|
`;
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
const unplugin = createUnplugin((opts) => {
|
|
119
130
|
const assets = /* @__PURE__ */ Object.create(null);
|
|
131
|
+
const _parseCache = /* @__PURE__ */ Object.create(null);
|
|
132
|
+
function parse(name, source) {
|
|
133
|
+
if (_parseCache[name]) {
|
|
134
|
+
return _parseCache[name];
|
|
135
|
+
}
|
|
136
|
+
const parsed = parseWasm(source);
|
|
137
|
+
const imports = /* @__PURE__ */ Object.create(null);
|
|
138
|
+
const exports = [];
|
|
139
|
+
for (const mod of parsed.modules) {
|
|
140
|
+
exports.push(...mod.exports.map((e) => e.name));
|
|
141
|
+
for (const imp of mod.imports) {
|
|
142
|
+
if (!imports[imp.module]) {
|
|
143
|
+
imports[imp.module] = [];
|
|
144
|
+
}
|
|
145
|
+
imports[imp.module].push(imp.name);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
_parseCache[name] = {
|
|
149
|
+
imports,
|
|
150
|
+
exports
|
|
151
|
+
};
|
|
152
|
+
return _parseCache[name];
|
|
153
|
+
}
|
|
120
154
|
return {
|
|
121
155
|
name: "unwasm",
|
|
122
156
|
rollup: {
|
|
@@ -136,8 +170,7 @@ const unplugin = createUnplugin((opts) => {
|
|
|
136
170
|
return {
|
|
137
171
|
id: r.id.startsWith("file://") ? r.id.slice(7) : r.id,
|
|
138
172
|
external: false,
|
|
139
|
-
moduleSideEffects: false
|
|
140
|
-
syntheticNamedExports: true
|
|
173
|
+
moduleSideEffects: false
|
|
141
174
|
};
|
|
142
175
|
}
|
|
143
176
|
}
|
|
@@ -163,7 +196,14 @@ const unplugin = createUnplugin((opts) => {
|
|
|
163
196
|
}
|
|
164
197
|
const source = await promises.readFile(id);
|
|
165
198
|
const name = `wasm/${basename(id, ".wasm")}-${sha1(source)}.wasm`;
|
|
166
|
-
|
|
199
|
+
const parsed = parse(name, source);
|
|
200
|
+
assets[id] = {
|
|
201
|
+
name,
|
|
202
|
+
id,
|
|
203
|
+
source,
|
|
204
|
+
imports: parsed.imports,
|
|
205
|
+
exports: parsed.exports
|
|
206
|
+
};
|
|
167
207
|
return `export default "UNWASM DUMMY EXPORT";`;
|
|
168
208
|
},
|
|
169
209
|
transform(_code, id) {
|
|
@@ -176,8 +216,7 @@ const unplugin = createUnplugin((opts) => {
|
|
|
176
216
|
}
|
|
177
217
|
return {
|
|
178
218
|
code: getWasmBinding(asset, opts),
|
|
179
|
-
map: { mappings: "" }
|
|
180
|
-
syntheticNamedExports: true
|
|
219
|
+
map: { mappings: "" }
|
|
181
220
|
};
|
|
182
221
|
},
|
|
183
222
|
renderChunk(code, chunk) {
|
|
@@ -230,4 +269,4 @@ const index = {
|
|
|
230
269
|
rollup
|
|
231
270
|
};
|
|
232
271
|
|
|
233
|
-
export { index as default };
|
|
272
|
+
export { index as default, rollup };
|
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/dist/tools.d.mts
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/dist/tools.d.ts
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/dist/tools.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { decode } from '@webassemblyjs/wasm-parser';
|
|
2
|
+
|
|
3
|
+
function parseWasm(source) {
|
|
4
|
+
const ast = decode(source);
|
|
5
|
+
const modules = [];
|
|
6
|
+
for (const body of ast.body) {
|
|
7
|
+
if (body.type === "Module") {
|
|
8
|
+
const module = {
|
|
9
|
+
imports: [],
|
|
10
|
+
exports: []
|
|
11
|
+
};
|
|
12
|
+
modules.push(module);
|
|
13
|
+
for (const field of body.fields) {
|
|
14
|
+
if (field.type === "ModuleImport") {
|
|
15
|
+
module.imports.push({
|
|
16
|
+
module: field.module,
|
|
17
|
+
name: field.name,
|
|
18
|
+
returnType: field.descr?.signature?.results?.[0],
|
|
19
|
+
params: field.descr.signature.params?.map(
|
|
20
|
+
(p) => ({
|
|
21
|
+
id: p.id,
|
|
22
|
+
type: p.valtype
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
});
|
|
26
|
+
} else if (field.type === "ModuleExport") {
|
|
27
|
+
module.exports.push({
|
|
28
|
+
name: field.name,
|
|
29
|
+
id: field.descr.id.value,
|
|
30
|
+
type: field.descr.exportType
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
modules
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { parseWasm };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unwasm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "WebAssembly tools for JavaScript",
|
|
5
5
|
"repository": "unjs/unwasm",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,8 +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
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"./tools": {
|
|
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
|
+
}
|
|
14
30
|
}
|
|
15
31
|
},
|
|
16
32
|
"files": [
|
|
@@ -30,6 +46,7 @@
|
|
|
30
46
|
"test:types": "tsc --noEmit --skipLibCheck"
|
|
31
47
|
},
|
|
32
48
|
"dependencies": {
|
|
49
|
+
"@webassemblyjs/wasm-parser": "^1.11.6",
|
|
33
50
|
"magic-string": "^0.30.5",
|
|
34
51
|
"mlly": "^1.4.2",
|
|
35
52
|
"pathe": "^1.1.1",
|
package/plugin.d.ts
CHANGED
package/dist/plugin/index.d.mts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Plugin } from 'rollup';
|
|
2
|
-
|
|
3
|
-
interface UnwasmPluginOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Direct import the wasm file instead of bundling, required in Cloudflare Workers
|
|
6
|
-
*
|
|
7
|
-
* @default false
|
|
8
|
-
*/
|
|
9
|
-
esmImport?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Import `.wasm` files using a lazily evaluated promise for compatibility with runtimes without top-level await support
|
|
12
|
-
*
|
|
13
|
-
* @default false
|
|
14
|
-
*/
|
|
15
|
-
lazy?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
declare const _default: {
|
|
19
|
-
rollup: (opts: UnwasmPluginOptions) => Plugin<any>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export { _default as default };
|
package/dist/plugin/index.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Plugin } from 'rollup';
|
|
2
|
-
|
|
3
|
-
interface UnwasmPluginOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Direct import the wasm file instead of bundling, required in Cloudflare Workers
|
|
6
|
-
*
|
|
7
|
-
* @default false
|
|
8
|
-
*/
|
|
9
|
-
esmImport?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Import `.wasm` files using a lazily evaluated promise for compatibility with runtimes without top-level await support
|
|
12
|
-
*
|
|
13
|
-
* @default false
|
|
14
|
-
*/
|
|
15
|
-
lazy?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
declare const _default: {
|
|
19
|
-
rollup: (opts: UnwasmPluginOptions) => Plugin<any>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export { _default as default };
|