From 33c3fdebb9a00fa331db0e400f6e89eef00ed668 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 12:35:01 +0100 Subject: [PATCH 1/9] initial support for non-inlined wasm --- packages/wasm/src/index.ts | 128 +++++++++++++++++++++++++++------ packages/wasm/test/test.js | 27 +++++++ packages/wasm/types/index.d.ts | 1 + 3 files changed, 134 insertions(+), 22 deletions(-) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 4e0ad4061..d71bf497e 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -1,34 +1,78 @@ -import { readFile } from 'fs'; -import { resolve } from 'path'; +import * as fs from 'fs'; +import * as path from 'path'; +import { promisify } from 'util'; +import { createHash } from 'crypto'; import { Plugin } from 'rollup'; import { RollupWasmOptions } from '../types'; +const makeDir = require('make-dir'); + +const fsStatPromise = promisify(fs.stat); +const fsReadFilePromise = promisify(fs.readFile); + export function wasm(options: RollupWasmOptions = {}): Plugin { - const syncFiles = (options.sync || []).map((x) => resolve(x)); + const { limit = 14 * 1024, sync = [] } = 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([fsStatPromise(id), fsReadFilePromise(id)]).then(([stats, buffer]) => { + if ((limit && stats.size > limit) || limit === 0) { + const hash = createHash('sha1') + .update(buffer) + .digest('hex') + .substr(0, 16); + + copies[id] = `${hash}.wasm`; + } + + 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) { + fs = require('fs') + path = 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,26 +84,66 @@ 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; + let filepath; + let src; + + if (isSync) { + filepath = null; + } else { + filepath = copies[id] ? `'${copies[id]}'` : null; + } + + if (filepath === null) { + src = Buffer.from(code, 'binary').toString('base64'); + src = `'${src}'`; + } else { + src = null; + } + + return `export default function(imports){return _loadWasmModule(${+isSync}, ${filepath}, ${src}, imports)}`; } return null; + }, + generateBundle: async function write(outputOptions) { + const base = outputOptions.dir || path.dirname(outputOptions.file); + + await makeDir(base); + + await Promise.all( + Object.keys(copies).map(async (name) => { + const output = copies[name]; + // Create a nested directory if the fileName pattern contains + // a directory structure + const outputDirectory = path.join(base, path.dirname(output)); + await makeDir(outputDirectory); + return copy(name, path.join(base, output)); + }) + ); } }; } +function copy(src, dest) { + return new Promise((resolve, reject) => { + const read = fs.createReadStream(src); + read.on('error', reject); + const write = fs.createWriteStream(dest); + write.on('error', reject); + write.on('finish', resolve); + read.pipe(write); + }); +} + export default wasm; diff --git a/packages/wasm/test/test.js b/packages/wasm/test/test.js index 849c1c971..2bc1c22dc 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; +const outputFile = 'output/bundle.js'; +const outputDir = 'output/'; + +test.beforeEach(() => del(outputDir)); + const testBundle = async (t, bundle) => { const code = await getCode(bundle); const func = new AsyncFunction('t', `let result;\n\n${code}\n\nreturn result;`); @@ -23,6 +32,24 @@ test('async compiling', async (t) => { await testBundle(t, bundle); }); +test('fetching WASM from separate file', async (t) => { + const bundle = await rollup({ + input: 'test/fixtures/complex.js', + plugins: [ + wasm({ + limit: 0 + }) + ] + }); + + await bundle.write({ format: 'es', file: outputFile }); + const glob = join(outputDir, `**/*.wasm`) + .split(sep) + .join(posix.sep); + + t.snapshot(await globby(glob)); +}); + test('complex module decoding', async (t) => { t.plan(2); diff --git a/packages/wasm/types/index.d.ts b/packages/wasm/types/index.d.ts index 54a8055d5..bdd8bcb72 100644 --- a/packages/wasm/types/index.d.ts +++ b/packages/wasm/types/index.d.ts @@ -5,6 +5,7 @@ export interface RollupWasmOptions { * Specifies an array of strings that each represent a WebAssembly file to load synchronously. */ sync?: readonly string[]; + limit?: Number; } /** From 33efb3d57e89f239b9b704bcb167e4ec3f0542f4 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 15:01:36 +0100 Subject: [PATCH 2/9] better document, fix output directory teardown --- packages/wasm/README.md | 22 +++++++++++++++++--- packages/wasm/src/index.ts | 24 ++++++++++++---------- packages/wasm/test/snapshots/test.js.md | 13 ++++++++++++ packages/wasm/test/snapshots/test.js.snap | Bin 0 -> 164 bytes packages/wasm/test/test.js | 21 ++++++++++--------- packages/wasm/types/index.d.ts | 9 ++++++++ 6 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 packages/wasm/test/snapshots/test.js.md create mode 100644 packages/wasm/test/snapshots/test.js.snap diff --git a/packages/wasm/README.md b/packages/wasm/README.md index ce9ff1360..2d25bc091 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. +### `limit`` + +Type: `Number`
+Default: `14336` (14kb) + +The file size limit for inline files. If a file exceeds this limit, it will be copied to the destination folder and the hashed filename will be provided instead. If `limit` 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 d71bf497e..81823230d 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -13,7 +13,7 @@ const fsStatPromise = promisify(fs.stat); const fsReadFilePromise = promisify(fs.readFile); export function wasm(options: RollupWasmOptions = {}): Plugin { - const { limit = 14 * 1024, sync = [] } = options; + const { sync = [], limit = 14 * 1024, publicPath = '' } = options; const syncFiles = sync.map((x) => path.resolve(x)); const copies = Object.create(null); @@ -33,7 +33,10 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { .digest('hex') .substr(0, 16); - copies[id] = `${hash}.wasm`; + // only copy if the file is not marked `sync`, `sync` files are always inlined + if (syncFiles.indexOf(id) === -1) { + copies[id] = `${publicPath}${hash}.wasm`; + } } return buffer.toString('binary'); @@ -57,8 +60,8 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null if (filepath && isNode) { - fs = require('fs') - path = require('path') + var fs = eval('require("fs")') + var path = eval('require("path")') return new Promise((resolve, reject) => { fs.readFile(path.resolve(__dirname, filepath), (error, buffer) => { @@ -96,15 +99,9 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { transform(code, id) { if (code && /\.wasm$/.test(id)) { const isSync = syncFiles.indexOf(id) !== -1; - let filepath; + const filepath = copies[id] ? `'${copies[id]}'` : null; let src; - if (isSync) { - filepath = null; - } else { - filepath = copies[id] ? `'${copies[id]}'` : null; - } - if (filepath === null) { src = Buffer.from(code, 'binary').toString('base64'); src = `'${src}'`; @@ -117,6 +114,11 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { return null; }, generateBundle: async function write(outputOptions) { + // can't generate anything if we can't determine the output base + if (!outputOptions.dir && !outputOptions.file) { + return; + } + const base = outputOptions.dir || path.dirname(outputOptions.file); await makeDir(base); 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 0000000000000000000000000000000000000000..2c8a3b47a9d8b93b99a98abc479dccbd8d29af8b GIT binary patch literal 164 zcmZ<^b5sbFn|GbZ~_pdJlMQ4 zZ&mQ*4wL%p6cSBg>=488aJY&!jy`NV&v#$<0CM%n7vqGzZrS5sXkwM11)ZCR9r)a$A0 QV$Ycvw7pAB>w#7S0ADRZjQ{`u literal 0 HcmV?d00001 diff --git a/packages/wasm/test/test.js b/packages/wasm/test/test.js index 2bc1c22dc..f660ed208 100755 --- a/packages/wasm/test/test.js +++ b/packages/wasm/test/test.js @@ -11,11 +11,11 @@ import wasm from '../'; const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; +process.chdir(__dirname); + const outputFile = 'output/bundle.js'; const outputDir = 'output/'; -test.beforeEach(() => del(outputDir)); - const testBundle = async (t, bundle) => { const code = await getCode(bundle); const func = new AsyncFunction('t', `let result;\n\n${code}\n\nreturn result;`); @@ -26,7 +26,7 @@ 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); @@ -34,7 +34,7 @@ test('async compiling', async (t) => { test('fetching WASM from separate file', async (t) => { const bundle = await rollup({ - input: 'test/fixtures/complex.js', + input: 'fixtures/complex.js', plugins: [ wasm({ limit: 0 @@ -48,13 +48,14 @@ test('fetching WASM from separate file', async (t) => { .join(posix.sep); 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); @@ -64,10 +65,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'] }) ] }); @@ -78,10 +79,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'] }) ] }); @@ -94,7 +95,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 bdd8bcb72..4d3e72b49 100644 --- a/packages/wasm/types/index.d.ts +++ b/packages/wasm/types/index.d.ts @@ -5,7 +5,16 @@ export interface RollupWasmOptions { * Specifies an array of strings that each represent a WebAssembly file to load synchronously. */ sync?: readonly string[]; + /** + * The file size limit to inline WebAssembly. If a file exceeds this limit, it will be copied to the destination folder and the hashed filename will be provided instead. + * If `limit` is set to `0` all files will be copied. + * Files specified in `sync` to load synchronously are always inlined, regardless of size. + */ limit?: Number; + /** + * A string which will be added in front of filenames when they are not inlined but are copied. + */ + publicPath?: string; } /** From ad2396ffa7206657393bdefc792c0e7778ab52ab Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 15:19:42 +0100 Subject: [PATCH 3/9] run code in test instead of only asserting wasm is emitted --- packages/wasm/test/test.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/wasm/test/test.js b/packages/wasm/test/test.js index f660ed208..da237bf79 100755 --- a/packages/wasm/test/test.js +++ b/packages/wasm/test/test.js @@ -13,8 +13,8 @@ const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; process.chdir(__dirname); -const outputFile = 'output/bundle.js'; -const outputDir = 'output/'; +const outputFile = './output/bundle.js'; +const outputDir = './output/'; const testBundle = async (t, bundle) => { const code = await getCode(bundle); @@ -33,6 +33,8 @@ test('async compiling', async (t) => { }); test('fetching WASM from separate file', async (t) => { + t.plan(3); + const bundle = await rollup({ input: 'fixtures/complex.js', plugins: [ @@ -42,11 +44,16 @@ test('fetching WASM from separate file', async (t) => { ] }); - await bundle.write({ format: 'es', file: outputFile }); + 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); }); From 1c0d64705171f56eb70adc0f0275c919a563f0c5 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 15:27:51 +0100 Subject: [PATCH 4/9] fix formatting issue, add assertion --- packages/wasm/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 81823230d..8f963de43 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -3,6 +3,8 @@ import * as path from 'path'; import { promisify } from 'util'; import { createHash } from 'crypto'; +import { assert } from 'console'; + import { Plugin } from 'rollup'; import { RollupWasmOptions } from '../types'; @@ -103,6 +105,7 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { let src; if (filepath === null) { + assert(!isSync, 'non-inlined files can not be `sync`.'); src = Buffer.from(code, 'binary').toString('base64'); src = `'${src}'`; } else { From 95024860f968c12b2ee98aef5c1910d81cb6718f Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 15:33:05 +0100 Subject: [PATCH 5/9] doc clarification for non-inline files --- packages/wasm/README.md | 4 ++-- packages/wasm/types/index.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/wasm/README.md b/packages/wasm/README.md index 2d25bc091..c80e4353f 100755 --- a/packages/wasm/README.md +++ b/packages/wasm/README.md @@ -53,12 +53,12 @@ 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. -### `limit`` +### `limit` Type: `Number`
Default: `14336` (14kb) -The file size limit for inline files. If a file exceeds this limit, it will be copied to the destination folder and the hashed filename will be provided instead. If `limit` is set to `0` all files will be copied. +The file size limit 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 `limit` is set to `0` all files will be copied. Files specified in `sync` to load synchronously are always inlined, regardless of size. diff --git a/packages/wasm/types/index.d.ts b/packages/wasm/types/index.d.ts index 4d3e72b49..c8d7e23a0 100644 --- a/packages/wasm/types/index.d.ts +++ b/packages/wasm/types/index.d.ts @@ -6,7 +6,7 @@ export interface RollupWasmOptions { */ sync?: readonly string[]; /** - * The file size limit to inline WebAssembly. If a file exceeds this limit, it will be copied to the destination folder and the hashed filename will be provided instead. + * The file size limit 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 `limit` is set to `0` all files will be copied. * Files specified in `sync` to load synchronously are always inlined, regardless of size. */ From 7b91b89457c8dfecb58307bc3707d70f7e2af439 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 18:05:32 +0100 Subject: [PATCH 6/9] assert -> this.error --- packages/wasm/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 8f963de43..a9a18f0df 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -3,8 +3,6 @@ import * as path from 'path'; import { promisify } from 'util'; import { createHash } from 'crypto'; -import { assert } from 'console'; - import { Plugin } from 'rollup'; import { RollupWasmOptions } from '../types'; @@ -105,10 +103,12 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { let src; if (filepath === null) { - assert(!isSync, 'non-inlined files can not be `sync`.'); src = Buffer.from(code, 'binary').toString('base64'); src = `'${src}'`; } else { + if (isSync) { + this.error('non-inlined files can not be `sync`.'); + } src = null; } From 46eebb803bc376cb5d56b30d17d393b5a02082a4 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Sun, 16 Aug 2020 18:14:08 +0100 Subject: [PATCH 7/9] limit -> maxFileSize --- packages/wasm/README.md | 4 ++-- packages/wasm/src/index.ts | 4 ++-- packages/wasm/test/test.js | 2 +- packages/wasm/types/index.d.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/wasm/README.md b/packages/wasm/README.md index c80e4353f..7a6c6393b 100755 --- a/packages/wasm/README.md +++ b/packages/wasm/README.md @@ -53,12 +53,12 @@ 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. -### `limit` +### `maxFileSize` Type: `Number`
Default: `14336` (14kb) -The file size limit 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 `limit` is set to `0` all files will be copied. +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. diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index a9a18f0df..984cfb752 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -13,7 +13,7 @@ const fsStatPromise = promisify(fs.stat); const fsReadFilePromise = promisify(fs.readFile); export function wasm(options: RollupWasmOptions = {}): Plugin { - const { sync = [], limit = 14 * 1024, publicPath = '' } = options; + const { sync = [], maxFileSize = 14 * 1024, publicPath = '' } = options; const syncFiles = sync.map((x) => path.resolve(x)); const copies = Object.create(null); @@ -27,7 +27,7 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { } return Promise.all([fsStatPromise(id), fsReadFilePromise(id)]).then(([stats, buffer]) => { - if ((limit && stats.size > limit) || limit === 0) { + if ((maxFileSize && stats.size > maxFileSize) || maxFileSize === 0) { const hash = createHash('sha1') .update(buffer) .digest('hex') diff --git a/packages/wasm/test/test.js b/packages/wasm/test/test.js index da237bf79..6e794df40 100755 --- a/packages/wasm/test/test.js +++ b/packages/wasm/test/test.js @@ -39,7 +39,7 @@ test('fetching WASM from separate file', async (t) => { input: 'fixtures/complex.js', plugins: [ wasm({ - limit: 0 + maxFileSize: 0 }) ] }); diff --git a/packages/wasm/types/index.d.ts b/packages/wasm/types/index.d.ts index c8d7e23a0..a80a9d8f4 100644 --- a/packages/wasm/types/index.d.ts +++ b/packages/wasm/types/index.d.ts @@ -6,11 +6,11 @@ export interface RollupWasmOptions { */ sync?: readonly string[]; /** - * The file size limit 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 `limit` is set to `0` all files will be copied. + * 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. */ - limit?: Number; + maxFileSize?: Number; /** * A string which will be added in front of filenames when they are not inlined but are copied. */ From 2f965f0a69417f4dd346053da9fc879f12394c76 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Mon, 17 Aug 2020 18:07:02 +0100 Subject: [PATCH 8/9] use this.emitFile instead of manual copying --- packages/wasm/src/index.ts | 53 ++++++++++++++------------------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 984cfb752..0a9c82178 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -7,8 +7,6 @@ import { Plugin } from 'rollup'; import { RollupWasmOptions } from '../types'; -const makeDir = require('make-dir'); - const fsStatPromise = promisify(fs.stat); const fsReadFilePromise = promisify(fs.readFile); @@ -33,9 +31,16 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { .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] = `${publicPath}${hash}.wasm`; + copies[id] = { + filename, + publicFilepath, + buffer + }; } } @@ -99,10 +104,10 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { transform(code, id) { if (code && /\.wasm$/.test(id)) { const isSync = syncFiles.indexOf(id) !== -1; - const filepath = copies[id] ? `'${copies[id]}'` : null; + const publicFilepath = copies[id] ? `'${copies[id].publicFilepath}'` : null; let src; - if (filepath === null) { + if (publicFilepath === null) { src = Buffer.from(code, 'binary').toString('base64'); src = `'${src}'`; } else { @@ -112,43 +117,25 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { src = null; } - return `export default function(imports){return _loadWasmModule(${+isSync}, ${filepath}, ${src}, imports)}`; + return `export default function(imports){return _loadWasmModule(${+isSync}, ${publicFilepath}, ${src}, imports)}`; } return null; }, - generateBundle: async function write(outputOptions) { - // can't generate anything if we can't determine the output base - if (!outputOptions.dir && !outputOptions.file) { - return; - } - - const base = outputOptions.dir || path.dirname(outputOptions.file); - - await makeDir(base); - + generateBundle: async function write() { await Promise.all( Object.keys(copies).map(async (name) => { - const output = copies[name]; - // Create a nested directory if the fileName pattern contains - // a directory structure - const outputDirectory = path.join(base, path.dirname(output)); - await makeDir(outputDirectory); - return copy(name, path.join(base, output)); + const copy = copies[name]; + + this.emitFile({ + type: 'asset', + source: copy.buffer, + name: 'Rollup WASM Asset', + fileName: copy.filename + }); }) ); } }; } -function copy(src, dest) { - return new Promise((resolve, reject) => { - const read = fs.createReadStream(src); - read.on('error', reject); - const write = fs.createWriteStream(dest); - write.on('error', reject); - write.on('finish', resolve); - read.pipe(write); - }); -} - export default wasm; From 1d0e1ca33599eebcc6c1824877389836c9fe17f8 Mon Sep 17 00:00:00 2001 From: Benjamin Minixhofer Date: Mon, 17 Aug 2020 19:41:21 +0100 Subject: [PATCH 9/9] switch to fs promise API --- packages/wasm/src/index.ts | 46 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 0a9c82178..a15267bc9 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -1,15 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import { promisify } from 'util'; import { createHash } from 'crypto'; import { Plugin } from 'rollup'; import { RollupWasmOptions } from '../types'; -const fsStatPromise = promisify(fs.stat); -const fsReadFilePromise = promisify(fs.readFile); - export function wasm(options: RollupWasmOptions = {}): Plugin { const { sync = [], maxFileSize = 14 * 1024, publicPath = '' } = options; @@ -24,28 +20,30 @@ export function wasm(options: RollupWasmOptions = {}): Plugin { return null; } - return Promise.all([fsStatPromise(id), fsReadFilePromise(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 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'); - }); + return buffer.toString('binary'); + } + ); }, banner: `