diff --git a/packages/wasm/README.md b/packages/wasm/README.md
index ce9ff1360..7a6c6393b 100755
--- a/packages/wasm/README.md
+++ b/packages/wasm/README.md
@@ -36,9 +36,9 @@ export default {
input: 'src/index.js',
output: {
dir: 'output',
- format: 'cjs'
+ format: 'cjs',
},
- plugins: [wasm()]
+ plugins: [wasm()],
};
```
@@ -53,6 +53,22 @@ Default: `null`
Specifies an array of strings that each represent a WebAssembly file to load synchronously. See [Synchronous Modules](#synchronous-modules) for a functional example.
+### `maxFileSize`
+
+Type: `Number`
+Default: `14336` (14kb)
+
+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.
+
+Files specified in `sync` to load synchronously are always inlined, regardless of size.
+
+### `publicPath`
+
+Type: `String`
+Default: (empty string)
+
+A string which will be added in front of filenames when they are not inlined but are copied.
+
## WebAssembly Example
Given the following simple C file:
@@ -83,7 +99,7 @@ Small modules (< 4KB) can be compiled synchronously by specifying them in the co
```js
wasm({
- sync: ['web/sample.wasm', 'web/foobar.wasm']
+ sync: ['web/sample.wasm', 'web/foobar.wasm'],
});
```
diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts
index 4e0ad4061..a15267bc9 100644
--- a/packages/wasm/src/index.ts
+++ b/packages/wasm/src/index.ts
@@ -1,34 +1,84 @@
-import { readFile } from 'fs';
-import { resolve } from 'path';
+import * as fs from 'fs';
+import * as path from 'path';
+import { createHash } from 'crypto';
import { Plugin } from 'rollup';
import { RollupWasmOptions } from '../types';
export function wasm(options: RollupWasmOptions = {}): Plugin {
- const syncFiles = (options.sync || []).map((x) => resolve(x));
+ const { sync = [], maxFileSize = 14 * 1024, publicPath = '' } = options;
+
+ const syncFiles = sync.map((x) => path.resolve(x));
+ const copies = Object.create(null);
return {
name: 'wasm',
load(id) {
- if (/\.wasm$/.test(id)) {
- return new Promise((res, reject) => {
- readFile(id, (error, buffer) => {
- if (error != null) {
- reject(error);
- }
- res(buffer.toString('binary'));
- });
- });
+ if (!/\.wasm$/.test(id)) {
+ return null;
}
- return null;
+
+ return Promise.all([fs.promises.stat(id), fs.promises.readFile(id)]).then(
+ ([stats, buffer]) => {
+ if ((maxFileSize && stats.size > maxFileSize) || maxFileSize === 0) {
+ const hash = createHash('sha1')
+ .update(buffer)
+ .digest('hex')
+ .substr(0, 16);
+
+ const filename = `${hash}.wasm`;
+ const publicFilepath = `${publicPath}${filename}`;
+
+ // only copy if the file is not marked `sync`, `sync` files are always inlined
+ if (syncFiles.indexOf(id) === -1) {
+ copies[id] = {
+ filename,
+ publicFilepath,
+ buffer
+ };
+ }
+ }
+
+ return buffer.toString('binary');
+ }
+ );
},
banner: `
- function _loadWasmModule (sync, src, imports) {
+ function _loadWasmModule (sync, filepath, src, imports) {
+ function _instantiateOrCompile(source, imports, stream) {
+ var instantiateFunc = stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate;
+ var compileFunc = stream ? WebAssembly.compileStreaming : WebAssembly.compile;
+
+ if (imports) {
+ return instantiateFunc(source, imports)
+ } else {
+ return compileFunc(source)
+ }
+ }
+
var buf = null
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
+
+ if (filepath && isNode) {
+ var fs = eval('require("fs")')
+ var path = eval('require("path")')
+
+ return new Promise((resolve, reject) => {
+ fs.readFile(path.resolve(__dirname, filepath), (error, buffer) => {
+ if (error != null) {
+ reject(error)
+ }
+
+ resolve(_instantiateOrCompile(buffer, imports, false))
+ });
+ });
+ } else if (filepath) {
+ return _instantiateOrCompile(fetch(filepath), imports, true)
+ }
+
if (isNode) {
buf = Buffer.from(src, 'base64')
} else {
@@ -40,24 +90,48 @@ export function wasm(options: RollupWasmOptions = {}): Plugin {
}
}
- if (imports && !sync) {
- return WebAssembly.instantiate(buf, imports)
- } else if (!imports && !sync) {
- return WebAssembly.compile(buf)
- } else {
+ if(sync) {
var mod = new WebAssembly.Module(buf)
return imports ? new WebAssembly.Instance(mod, imports) : mod
+ } else {
+ return _instantiateOrCompile(buf, imports, false)
}
}
`.trim(),
transform(code, id) {
if (code && /\.wasm$/.test(id)) {
- const src = Buffer.from(code, 'binary').toString('base64');
- const sync = syncFiles.indexOf(id) !== -1;
- return `export default function(imports){return _loadWasmModule(${+sync}, '${src}', imports)}`;
+ const isSync = syncFiles.indexOf(id) !== -1;
+ const publicFilepath = copies[id] ? `'${copies[id].publicFilepath}'` : null;
+ let src;
+
+ if (publicFilepath === null) {
+ src = Buffer.from(code, 'binary').toString('base64');
+ src = `'${src}'`;
+ } else {
+ if (isSync) {
+ this.error('non-inlined files can not be `sync`.');
+ }
+ src = null;
+ }
+
+ return `export default function(imports){return _loadWasmModule(${+isSync}, ${publicFilepath}, ${src}, imports)}`;
}
return null;
+ },
+ generateBundle: async function write() {
+ await Promise.all(
+ Object.keys(copies).map(async (name) => {
+ const copy = copies[name];
+
+ this.emitFile({
+ type: 'asset',
+ source: copy.buffer,
+ name: 'Rollup WASM Asset',
+ fileName: copy.filename
+ });
+ })
+ );
}
};
}
diff --git a/packages/wasm/test/snapshots/test.js.md b/packages/wasm/test/snapshots/test.js.md
new file mode 100644
index 000000000..b6a228c2a
--- /dev/null
+++ b/packages/wasm/test/snapshots/test.js.md
@@ -0,0 +1,13 @@
+# Snapshot report for `test/test.js`
+
+The actual snapshot is saved in `test.js.snap`.
+
+Generated by [AVA](https://avajs.dev).
+
+## fetching WASM from separate file
+
+> Snapshot 1
+
+ [
+ 'output/85cebae0fa1ae813.wasm',
+ ]
diff --git a/packages/wasm/test/snapshots/test.js.snap b/packages/wasm/test/snapshots/test.js.snap
new file mode 100644
index 000000000..2c8a3b47a
Binary files /dev/null and b/packages/wasm/test/snapshots/test.js.snap differ
diff --git a/packages/wasm/test/test.js b/packages/wasm/test/test.js
index 849c1c971..6e794df40 100755
--- a/packages/wasm/test/test.js
+++ b/packages/wasm/test/test.js
@@ -1,5 +1,9 @@
+import { sep, posix, join } from 'path';
+
import { rollup } from 'rollup';
+import globby from 'globby';
import test from 'ava';
+import del from 'del';
import { getCode } from '../../../util/test';
@@ -7,6 +11,11 @@ import wasm from '../';
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
+process.chdir(__dirname);
+
+const outputFile = './output/bundle.js';
+const outputDir = './output/';
+
const testBundle = async (t, bundle) => {
const code = await getCode(bundle);
const func = new AsyncFunction('t', `let result;\n\n${code}\n\nreturn result;`);
@@ -17,17 +26,43 @@ test('async compiling', async (t) => {
t.plan(2);
const bundle = await rollup({
- input: 'test/fixtures/async.js',
+ input: 'fixtures/async.js',
plugins: [wasm()]
});
await testBundle(t, bundle);
});
+test('fetching WASM from separate file', async (t) => {
+ t.plan(3);
+
+ const bundle = await rollup({
+ input: 'fixtures/complex.js',
+ plugins: [
+ wasm({
+ maxFileSize: 0
+ })
+ ]
+ });
+
+ await bundle.write({ format: 'cjs', file: outputFile });
+ const glob = join(outputDir, `**/*.wasm`)
+ .split(sep)
+ .join(posix.sep);
+
+ global.result = null;
+ global.t = t;
+ require(outputFile);
+
+ await global.result;
+ t.snapshot(await globby(glob));
+ await del(outputDir);
+});
+
test('complex module decoding', async (t) => {
t.plan(2);
const bundle = await rollup({
- input: 'test/fixtures/complex.js',
+ input: 'fixtures/complex.js',
plugins: [wasm()]
});
await testBundle(t, bundle);
@@ -37,10 +72,10 @@ test('sync compiling', async (t) => {
t.plan(2);
const bundle = await rollup({
- input: 'test/fixtures/sync.js',
+ input: 'fixtures/sync.js',
plugins: [
wasm({
- sync: ['test/fixtures/sample.wasm']
+ sync: ['fixtures/sample.wasm']
})
]
});
@@ -51,10 +86,10 @@ test('imports', async (t) => {
t.plan(1);
const bundle = await rollup({
- input: 'test/fixtures/imports.js',
+ input: 'fixtures/imports.js',
plugins: [
wasm({
- sync: ['test/fixtures/imports.wasm']
+ sync: ['fixtures/imports.wasm']
})
]
});
@@ -67,7 +102,7 @@ try {
t.plan(2);
const bundle = await rollup({
- input: 'test/fixtures/worker.js',
+ input: 'fixtures/worker.js',
plugins: [wasm()]
});
const code = await getCode(bundle);
diff --git a/packages/wasm/types/index.d.ts b/packages/wasm/types/index.d.ts
index 54a8055d5..a80a9d8f4 100644
--- a/packages/wasm/types/index.d.ts
+++ b/packages/wasm/types/index.d.ts
@@ -5,6 +5,16 @@ export interface RollupWasmOptions {
* Specifies an array of strings that each represent a WebAssembly file to load synchronously.
*/
sync?: readonly string[];
+ /**
+ * 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.
+ * Files specified in `sync` to load synchronously are always inlined, regardless of size.
+ */
+ maxFileSize?: Number;
+ /**
+ * A string which will be added in front of filenames when they are not inlined but are copied.
+ */
+ publicPath?: string;
}
/**