Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e747cd

Browse filesBrowse files
authored
feat(wasm): option to not inline wasm (#543)
* initial support for non-inlined wasm * better document, fix output directory teardown * run code in test instead of only asserting wasm is emitted * fix formatting issue, add assertion * doc clarification for non-inline files * assert -> this.error * limit -> maxFileSize * use this.emitFile instead of manual copying * switch to fs promise API
1 parent c522744 commit 0e747cd
Copy full SHA for 0e747cd

File tree

Expand file treeCollapse file tree

6 files changed

+180
-32
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+180
-32
lines changed

‎packages/wasm/README.md

Copy file name to clipboardExpand all lines: packages/wasm/README.md
+19-3Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ export default {
3636
input: 'src/index.js',
3737
output: {
3838
dir: 'output',
39-
format: 'cjs'
39+
format: 'cjs',
4040
},
41-
plugins: [wasm()]
41+
plugins: [wasm()],
4242
};
4343
```
4444

@@ -53,6 +53,22 @@ Default: `null`
5353

5454
Specifies an array of strings that each represent a WebAssembly file to load synchronously. See [Synchronous Modules](#synchronous-modules) for a functional example.
5555

56+
### `maxFileSize`
57+
58+
Type: `Number`<br>
59+
Default: `14336` (14kb)
60+
61+
The maximum file size for inline files. If a file exceeds this limit, it will be copied to the destination folder and loaded from a separate file at runtime. If `maxFileSize` is set to `0` all files will be copied.
62+
63+
Files specified in `sync` to load synchronously are always inlined, regardless of size.
64+
65+
### `publicPath`
66+
67+
Type: `String`<br>
68+
Default: (empty string)
69+
70+
A string which will be added in front of filenames when they are not inlined but are copied.
71+
5672
## WebAssembly Example
5773

5874
Given the following simple C file:
@@ -83,7 +99,7 @@ Small modules (< 4KB) can be compiled synchronously by specifying them in the co
8399

84100
```js
85101
wasm({
86-
sync: ['web/sample.wasm', 'web/foobar.wasm']
102+
sync: ['web/sample.wasm', 'web/foobar.wasm'],
87103
});
88104
```
89105

‎packages/wasm/src/index.ts

Copy file name to clipboardExpand all lines: packages/wasm/src/index.ts
+96-22Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,84 @@
1-
import { readFile } from 'fs';
2-
import { resolve } from 'path';
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { createHash } from 'crypto';
34

45
import { Plugin } from 'rollup';
56

67
import { RollupWasmOptions } from '../types';
78

89
export function wasm(options: RollupWasmOptions = {}): Plugin {
9-
const syncFiles = (options.sync || []).map((x) => resolve(x));
10+
const { sync = [], maxFileSize = 14 * 1024, publicPath = '' } = options;
11+
12+
const syncFiles = sync.map((x) => path.resolve(x));
13+
const copies = Object.create(null);
1014

1115
return {
1216
name: 'wasm',
1317

1418
load(id) {
15-
if (/\.wasm$/.test(id)) {
16-
return new Promise((res, reject) => {
17-
readFile(id, (error, buffer) => {
18-
if (error != null) {
19-
reject(error);
20-
}
21-
res(buffer.toString('binary'));
22-
});
23-
});
19+
if (!/\.wasm$/.test(id)) {
20+
return null;
2421
}
25-
return null;
22+
23+
return Promise.all([fs.promises.stat(id), fs.promises.readFile(id)]).then(
24+
([stats, buffer]) => {
25+
if ((maxFileSize && stats.size > maxFileSize) || maxFileSize === 0) {
26+
const hash = createHash('sha1')
27+
.update(buffer)
28+
.digest('hex')
29+
.substr(0, 16);
30+
31+
const filename = `${hash}.wasm`;
32+
const publicFilepath = `${publicPath}${filename}`;
33+
34+
// only copy if the file is not marked `sync`, `sync` files are always inlined
35+
if (syncFiles.indexOf(id) === -1) {
36+
copies[id] = {
37+
filename,
38+
publicFilepath,
39+
buffer
40+
};
41+
}
42+
}
43+
44+
return buffer.toString('binary');
45+
}
46+
);
2647
},
2748

2849
banner: `
29-
function _loadWasmModule (sync, src, imports) {
50+
function _loadWasmModule (sync, filepath, src, imports) {
51+
function _instantiateOrCompile(source, imports, stream) {
52+
var instantiateFunc = stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate;
53+
var compileFunc = stream ? WebAssembly.compileStreaming : WebAssembly.compile;
54+
55+
if (imports) {
56+
return instantiateFunc(source, imports)
57+
} else {
58+
return compileFunc(source)
59+
}
60+
}
61+
3062
var buf = null
3163
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
64+
65+
if (filepath && isNode) {
66+
var fs = eval('require("fs")')
67+
var path = eval('require("path")')
68+
69+
return new Promise((resolve, reject) => {
70+
fs.readFile(path.resolve(__dirname, filepath), (error, buffer) => {
71+
if (error != null) {
72+
reject(error)
73+
}
74+
75+
resolve(_instantiateOrCompile(buffer, imports, false))
76+
});
77+
});
78+
} else if (filepath) {
79+
return _instantiateOrCompile(fetch(filepath), imports, true)
80+
}
81+
3282
if (isNode) {
3383
buf = Buffer.from(src, 'base64')
3484
} else {
@@ -40,24 +90,48 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
4090
}
4191
}
4292
43-
if (imports && !sync) {
44-
return WebAssembly.instantiate(buf, imports)
45-
} else if (!imports && !sync) {
46-
return WebAssembly.compile(buf)
47-
} else {
93+
if(sync) {
4894
var mod = new WebAssembly.Module(buf)
4995
return imports ? new WebAssembly.Instance(mod, imports) : mod
96+
} else {
97+
return _instantiateOrCompile(buf, imports, false)
5098
}
5199
}
52100
`.trim(),
53101

54102
transform(code, id) {
55103
if (code && /\.wasm$/.test(id)) {
56-
const src = Buffer.from(code, 'binary').toString('base64');
57-
const sync = syncFiles.indexOf(id) !== -1;
58-
return `export default function(imports){return _loadWasmModule(${+sync}, '${src}', imports)}`;
104+
const isSync = syncFiles.indexOf(id) !== -1;
105+
const publicFilepath = copies[id] ? `'${copies[id].publicFilepath}'` : null;
106+
let src;
107+
108+
if (publicFilepath === null) {
109+
src = Buffer.from(code, 'binary').toString('base64');
110+
src = `'${src}'`;
111+
} else {
112+
if (isSync) {
113+
this.error('non-inlined files can not be `sync`.');
114+
}
115+
src = null;
116+
}
117+
118+
return `export default function(imports){return _loadWasmModule(${+isSync}, ${publicFilepath}, ${src}, imports)}`;
59119
}
60120
return null;
121+
},
122+
generateBundle: async function write() {
123+
await Promise.all(
124+
Object.keys(copies).map(async (name) => {
125+
const copy = copies[name];
126+
127+
this.emitFile({
128+
type: 'asset',
129+
source: copy.buffer,
130+
name: 'Rollup WASM Asset',
131+
fileName: copy.filename
132+
});
133+
})
134+
);
61135
}
62136
};
63137
}
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Snapshot report for `test/test.js`
2+
3+
The actual snapshot is saved in `test.js.snap`.
4+
5+
Generated by [AVA](https://avajs.dev).
6+
7+
## fetching WASM from separate file
8+
9+
> Snapshot 1
10+
11+
[
12+
'output/85cebae0fa1ae813.wasm',
13+
]
164 Bytes
Binary file not shown.

‎packages/wasm/test/test.js

Copy file name to clipboardExpand all lines: packages/wasm/test/test.js
+42-7Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1+
import { sep, posix, join } from 'path';
2+
13
import { rollup } from 'rollup';
4+
import globby from 'globby';
25
import test from 'ava';
6+
import del from 'del';
37

48
import { getCode } from '../../../util/test';
59

610
import wasm from '../';
711

812
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
913

14+
process.chdir(__dirname);
15+
16+
const outputFile = './output/bundle.js';
17+
const outputDir = './output/';
18+
1019
const testBundle = async (t, bundle) => {
1120
const code = await getCode(bundle);
1221
const func = new AsyncFunction('t', `let result;\n\n${code}\n\nreturn result;`);
@@ -17,17 +26,43 @@ test('async compiling', async (t) => {
1726
t.plan(2);
1827

1928
const bundle = await rollup({
20-
input: 'test/fixtures/async.js',
29+
input: 'fixtures/async.js',
2130
plugins: [wasm()]
2231
});
2332
await testBundle(t, bundle);
2433
});
2534

35+
test('fetching WASM from separate file', async (t) => {
36+
t.plan(3);
37+
38+
const bundle = await rollup({
39+
input: 'fixtures/complex.js',
40+
plugins: [
41+
wasm({
42+
maxFileSize: 0
43+
})
44+
]
45+
});
46+
47+
await bundle.write({ format: 'cjs', file: outputFile });
48+
const glob = join(outputDir, `**/*.wasm`)
49+
.split(sep)
50+
.join(posix.sep);
51+
52+
global.result = null;
53+
global.t = t;
54+
require(outputFile);
55+
56+
await global.result;
57+
t.snapshot(await globby(glob));
58+
await del(outputDir);
59+
});
60+
2661
test('complex module decoding', async (t) => {
2762
t.plan(2);
2863

2964
const bundle = await rollup({
30-
input: 'test/fixtures/complex.js',
65+
input: 'fixtures/complex.js',
3166
plugins: [wasm()]
3267
});
3368
await testBundle(t, bundle);
@@ -37,10 +72,10 @@ test('sync compiling', async (t) => {
3772
t.plan(2);
3873

3974
const bundle = await rollup({
40-
input: 'test/fixtures/sync.js',
75+
input: 'fixtures/sync.js',
4176
plugins: [
4277
wasm({
43-
sync: ['test/fixtures/sample.wasm']
78+
sync: ['fixtures/sample.wasm']
4479
})
4580
]
4681
});
@@ -51,10 +86,10 @@ test('imports', async (t) => {
5186
t.plan(1);
5287

5388
const bundle = await rollup({
54-
input: 'test/fixtures/imports.js',
89+
input: 'fixtures/imports.js',
5590
plugins: [
5691
wasm({
57-
sync: ['test/fixtures/imports.wasm']
92+
sync: ['fixtures/imports.wasm']
5893
})
5994
]
6095
});
@@ -67,7 +102,7 @@ try {
67102
t.plan(2);
68103

69104
const bundle = await rollup({
70-
input: 'test/fixtures/worker.js',
105+
input: 'fixtures/worker.js',
71106
plugins: [wasm()]
72107
});
73108
const code = await getCode(bundle);

‎packages/wasm/types/index.d.ts

Copy file name to clipboardExpand all lines: packages/wasm/types/index.d.ts
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ export interface RollupWasmOptions {
55
* Specifies an array of strings that each represent a WebAssembly file to load synchronously.
66
*/
77
sync?: readonly string[];
8+
/**
9+
* The maximum file size for inline files. If a file exceeds this limit, it will be copied to the destination folder and loaded from a separate file at runtime.
10+
* If `maxFileSize` is set to `0` all files will be copied.
11+
* Files specified in `sync` to load synchronously are always inlined, regardless of size.
12+
*/
13+
maxFileSize?: Number;
14+
/**
15+
* A string which will be added in front of filenames when they are not inlined but are copied.
16+
*/
17+
publicPath?: string;
818
}
919

1020
/**

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.