Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 50b618b

Browse files
nickbabcockshellscape
authored andcommitted
feat(wasm): Add target environment configuration (#1132)
Currently the Wasm rollup plugin emits code that includes calls to require builtin node modules (`fs` and `path`) as well as builtin `fetch` in browser environments. Even though there is an check before the environment specific code is executed, the mere presence of `require` for builtin node modules will cause downstream bundlers like `esbuild` and `webpack` to emit errors when they statically analyze the output. This commit adds in a `targetEnv` that configures what code is emitted to instantiate the Wasm (both inline and separate) - `"auto"` will determine the environment at runtime and invoke the correct methods accordingly - `"auto-inline"` always inlines the Wasm and will decode it according to the environment - `"browser"` omits emitting code that requires node.js builtin modules that may play havoc on downstream bundlers - `"node"` omits emitting code that requires `fetch` "auto" is the default behavior and preserves backwards compatibility.
1 parent e5db668 commit 50b618b

File tree

5 files changed

+181
-33
lines changed

5 files changed

+181
-33
lines changed

packages/wasm/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ Default: (empty string)
6969

7070
A string which will be added in front of filenames when they are not inlined but are copied.
7171

72+
### `targetEnv`
73+
74+
Type: `"auto" | "browser" | "node"`<br>
75+
Default: `"auto"`
76+
77+
Configures what code is emitted to instantiate the Wasm (both inline and separate):
78+
79+
- `"auto"` will determine the environment at runtime and invoke the correct methods accordingly
80+
- `"auto-inline"` always inlines the Wasm and will decode it according to the environment
81+
- `"browser"` omits emitting code that requires node.js builtin modules that may play havoc on downstream bundlers
82+
- `"node"` omits emitting code that requires `fetch`
83+
7284
## WebAssembly Example
7385

7486
Given the following simple C file:

packages/wasm/src/helper.ts

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,100 @@
1+
import { TargetEnv } from '../types';
2+
13
export const HELPERS_ID = '\0wasmHelpers.js';
24

3-
export const getHelpersModule = () => `
5+
const nodeFilePath = `
6+
var fs = require("fs")
7+
var path = require("path")
8+
9+
return new Promise((resolve, reject) => {
10+
fs.readFile(path.resolve(__dirname, filepath), (error, buffer) => {
11+
if (error != null) {
12+
reject(error)
13+
}
14+
15+
resolve(_instantiateOrCompile(buffer, imports, false))
16+
});
17+
});
18+
`;
19+
20+
const nodeDecode = `
21+
buf = Buffer.from(src, 'base64')
22+
`;
23+
24+
const browserFilePath = `
25+
return _instantiateOrCompile(fetch(filepath), imports, true);
26+
`;
27+
28+
const browserDecode = `
29+
var raw = globalThis.atob(src)
30+
var rawLength = raw.length
31+
buf = new Uint8Array(new ArrayBuffer(rawLength))
32+
for(var i = 0; i < rawLength; i++) {
33+
buf[i] = raw.charCodeAt(i)
34+
}
35+
`;
36+
37+
const autoModule = `
38+
var buf = null
39+
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
40+
41+
if (filepath && isNode) {
42+
${nodeFilePath}
43+
} else if (filepath) {
44+
${browserFilePath}
45+
}
46+
47+
if (isNode) {
48+
${nodeDecode}
49+
} else {
50+
${browserDecode}
51+
}
52+
`;
53+
54+
const nodeModule = `
55+
var buf = null
56+
if (filepath) {
57+
${nodeFilePath}
58+
}
59+
60+
${nodeDecode}
61+
`;
62+
63+
const browserModule = `
64+
var buf = null
65+
if (filepath) {
66+
${browserFilePath}
67+
}
68+
69+
${browserDecode}
70+
`;
71+
72+
const autoInlineModule = `
73+
var buf = null
74+
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
75+
if (isNode) {
76+
${nodeDecode}
77+
} else {
78+
${browserDecode}
79+
}
80+
`;
81+
82+
const envModule = (env: TargetEnv) => {
83+
switch (env) {
84+
case 'auto':
85+
return autoModule;
86+
case 'auto-inline':
87+
return autoInlineModule;
88+
case 'browser':
89+
return browserModule;
90+
case 'node':
91+
return nodeModule;
92+
default:
93+
return null;
94+
}
95+
};
96+
97+
export const getHelpersModule = (env: TargetEnv) => `
498
function _loadWasmModule (sync, filepath, src, imports) {
599
function _instantiateOrCompile(source, imports, stream) {
6100
var instantiateFunc = stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate;
@@ -13,36 +107,7 @@ function _loadWasmModule (sync, filepath, src, imports) {
13107
}
14108
}
15109
16-
var buf = null
17-
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
18-
19-
if (filepath && isNode) {
20-
var fs = require("fs")
21-
var path = require("path")
22-
23-
return new Promise((resolve, reject) => {
24-
fs.readFile(path.resolve(__dirname, filepath), (error, buffer) => {
25-
if (error != null) {
26-
reject(error)
27-
}
28-
29-
resolve(_instantiateOrCompile(buffer, imports, false))
30-
});
31-
});
32-
} else if (filepath) {
33-
return _instantiateOrCompile(fetch(filepath), imports, true)
34-
}
35-
36-
if (isNode) {
37-
buf = Buffer.from(src, 'base64')
38-
} else {
39-
var raw = globalThis.atob(src)
40-
var rawLength = raw.length
41-
buf = new Uint8Array(new ArrayBuffer(rawLength))
42-
for(var i = 0; i < rawLength; i++) {
43-
buf[i] = raw.charCodeAt(i)
44-
}
45-
}
110+
${envModule(env)}
46111
47112
if(sync) {
48113
var mod = new WebAssembly.Module(buf)

packages/wasm/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RollupWasmOptions } from '../types';
99
import { getHelpersModule, HELPERS_ID } from './helper';
1010

1111
export function wasm(options: RollupWasmOptions = {}): Plugin {
12-
const { sync = [], maxFileSize = 14 * 1024, publicPath = '' } = options;
12+
const { sync = [], maxFileSize = 14 * 1024, publicPath = '', targetEnv = 'auto' } = options;
1313

1414
const syncFiles = sync.map((x) => path.resolve(x));
1515
const copies = Object.create(null);
@@ -27,7 +27,7 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
2727

2828
load(id) {
2929
if (id === HELPERS_ID) {
30-
return getHelpersModule();
30+
return getHelpersModule(targetEnv);
3131
}
3232

3333
if (!/\.wasm$/.test(id)) {
@@ -36,6 +36,10 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
3636

3737
return Promise.all([fs.promises.stat(id), fs.promises.readFile(id)]).then(
3838
([stats, buffer]) => {
39+
if (targetEnv === 'auto-inline') {
40+
return buffer.toString('binary');
41+
}
42+
3943
if ((maxFileSize && stats.size > maxFileSize) || maxFileSize === 0) {
4044
const hash = createHash('sha1').update(buffer).digest('hex').substr(0, 16);
4145

packages/wasm/test/test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,58 @@ test('injectHelper', async (t) => {
149149
});
150150
await testBundle(t, bundle);
151151
});
152+
153+
test('target environment auto', async (t) => {
154+
t.plan(5);
155+
156+
const bundle = await rollup({
157+
input: 'fixtures/async.js',
158+
plugins: [wasm({ targetEnv: 'auto' })]
159+
});
160+
const code = await getCode(bundle);
161+
await testBundle(t, bundle);
162+
t.true(code.includes(`require("fs")`));
163+
t.true(code.includes(`require("path")`));
164+
t.true(code.includes(`fetch`));
165+
});
166+
167+
test('target environment auto-inline', async (t) => {
168+
t.plan(6);
169+
170+
const bundle = await rollup({
171+
input: 'fixtures/async.js',
172+
plugins: [wasm({ targetEnv: 'auto-inline' })]
173+
});
174+
const code = await getCode(bundle);
175+
await testBundle(t, bundle);
176+
t.true(!code.includes(`require("fs")`));
177+
t.true(!code.includes(`require("path")`));
178+
t.true(!code.includes(`fetch`));
179+
t.true(code.includes(`if (isNode)`));
180+
});
181+
182+
test('target environment browser', async (t) => {
183+
t.plan(4);
184+
185+
const bundle = await rollup({
186+
input: 'fixtures/async.js',
187+
plugins: [wasm({ targetEnv: 'browser' })]
188+
});
189+
const code = await getCode(bundle);
190+
await testBundle(t, bundle);
191+
t.true(!code.includes(`require("`));
192+
t.true(code.includes(`fetch`));
193+
});
194+
195+
test('target environment node', async (t) => {
196+
t.plan(4);
197+
198+
const bundle = await rollup({
199+
input: 'fixtures/async.js',
200+
plugins: [wasm({ targetEnv: 'node' })]
201+
});
202+
const code = await getCode(bundle);
203+
await testBundle(t, bundle);
204+
t.true(code.includes(`require("`));
205+
t.true(!code.includes(`fetch`));
206+
});

packages/wasm/types/index.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { Plugin } from 'rollup';
22

3+
/**
4+
* - `"auto"` will determine the environment at runtime and invoke the correct methods accordingly
5+
* - `"auto-inline"` always inlines the Wasm and will decode it according to the environment
6+
* - `"browser"` omits emitting code that requires node.js builtin modules that may play havoc on downstream bundlers
7+
* - `"node"` omits emitting code that requires `fetch`
8+
*/
9+
export type TargetEnv = 'auto' | 'auto-inline' | 'browser' | 'node';
10+
311
export interface RollupWasmOptions {
412
/**
513
* Specifies an array of strings that each represent a WebAssembly file to load synchronously.
@@ -15,6 +23,10 @@ export interface RollupWasmOptions {
1523
* A string which will be added in front of filenames when they are not inlined but are copied.
1624
*/
1725
publicPath?: string;
26+
/**
27+
* Configures what code is emitted to instantiate the Wasm (both inline and separate)
28+
*/
29+
targetEnv?: TargetEnv;
1830
}
1931

2032
/**

0 commit comments

Comments
 (0)