From da02269834f1f944918c0ebc7d7f8829c3c048ef Mon Sep 17 00:00:00 2001 From: Arturas Date: Thu, 6 May 2021 23:46:31 +0100 Subject: [PATCH] feat: Add exposeWasm option --- README.md | 24 ++++++++ package-lock.json | 82 +++++++++++++++++++------ package.json | 1 + projects/core/autoinit-wasm-import.js | 33 +++++++--- projects/core/index.spec.ts | 87 +++++++++++++++++++-------- projects/core/index.ts | 13 +++- 6 files changed, 186 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 0e70b81..b9ffbe9 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ to breaking changes, although any such changes will follow semantic versioning. - [Requirements & basic setup](#requirements--basic-setup) - [The transformation](#the-transformation) - [The plugin config](#the-plugin-config) + - [Filtering](#filtering) + - [Raw wasm output](#raw-wasm-output) @@ -126,6 +128,13 @@ interface Filter { interface Opts { + /** + * When set to true, adds a __getWasm() function to the import() response which returns what the original + * init function returned + * @default false + */ + exposeWasm?: boolean; + /** * JS source files to look for dynamic imports in. * @default {include: /\.[jt]sx?$/} @@ -146,7 +155,22 @@ interface Opts { } ``` +## Filtering + There are two sets of files to match. Using the `my_lib` example from earlier, - `wasmFilter` should match `my_lib.js` - `jsFilter` should match files that'll contain the dynamic `import()`s + +## Raw wasm output + +By default, the response of the original init function is hidden. You can turn the `exposeWasm` option on to append +a `__getWasm` function to the module which will return it. If you're using Typescript, you can correct your typings by +adding the following to your Rust source files (constant name doesn't matter): + +```rust +#[wasm_bindgen(typescript_custom_section)] +const TS_APPEND_CONTENT: &'static str = r#" +export function __getWasm(): InitOutput; +"#; +``` diff --git a/package-lock.json b/package-lock.json index 96e1498..8e99094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@alorel/rollup-plugin-copy": "^1.0.2", "@alorel/rollup-plugin-copy-pkg-json": "^1.0.3", "@alorel/rollup-plugin-dts": "^2.0.1", + "@rollup/plugin-alias": "^3.1.2", "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/pluginutils": "^4.1.0", @@ -501,6 +502,21 @@ "@types/node": ">= 8" } }, + "node_modules/@rollup/plugin-alias": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.2.tgz", + "integrity": "sha512-wzDnQ6v7CcoRzS0qVwFPrFdYA4Qlr+ookA217Y2Z3DPZE1R8jrFNM3jvGgOf6o6DMjbnQIn5lCIJgHPe1Bt3uw==", + "dev": true, + "dependencies": { + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.0.tgz", @@ -4016,9 +4032,10 @@ } }, "node_modules/@semantic-release/release-notes-generator": { - "version": "9.0.1", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.2.tgz", + "integrity": "sha512-xGFSidhGqB27uwgWCU6y0gbf4r/no5flOAkJyFFc4+bPf8S+LfAVm7xhhlK5VPXLt2Iu1RBH8F+IgMK2ah5YpA==", "dev": true, - "license": "MIT", "dependencies": { "conventional-changelog-angular": "^5.0.0", "conventional-changelog-writer": "^4.0.0", @@ -4027,7 +4044,7 @@ "debug": "^4.0.0", "get-stream": "^5.0.0", "import-from": "^3.0.0", - "into-stream": "^5.0.0", + "into-stream": "^6.0.0", "lodash": "^4.17.4", "read-pkg-up": "^7.0.0" }, @@ -6162,8 +6179,9 @@ }, "node_modules/from2": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, - "license": "MIT", "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -6383,9 +6401,10 @@ } }, "node_modules/handlebars": { - "version": "4.7.6", + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.0", @@ -6469,9 +6488,10 @@ } }, "node_modules/hosted-git-info": { - "version": "2.8.8", - "dev": true, - "license": "ISC" + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true }, "node_modules/html-escaper": { "version": "2.0.2", @@ -6598,15 +6618,19 @@ "dev": true }, "node_modules/into-stream": { - "version": "5.1.1", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", "dev": true, - "license": "MIT", "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-alphabetical": { @@ -7985,8 +8009,9 @@ }, "node_modules/p-is-promise": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -10295,6 +10320,15 @@ "@types/node": ">= 8" } }, + "@rollup/plugin-alias": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.2.tgz", + "integrity": "sha512-wzDnQ6v7CcoRzS0qVwFPrFdYA4Qlr+ookA217Y2Z3DPZE1R8jrFNM3jvGgOf6o6DMjbnQIn5lCIJgHPe1Bt3uw==", + "dev": true, + "requires": { + "slash": "^3.0.0" + } + }, "@rollup/plugin-node-resolve": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.0.tgz", @@ -12699,7 +12733,9 @@ } }, "@semantic-release/release-notes-generator": { - "version": "9.0.1", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.2.tgz", + "integrity": "sha512-xGFSidhGqB27uwgWCU6y0gbf4r/no5flOAkJyFFc4+bPf8S+LfAVm7xhhlK5VPXLt2Iu1RBH8F+IgMK2ah5YpA==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -12709,7 +12745,7 @@ "debug": "^4.0.0", "get-stream": "^5.0.0", "import-from": "^3.0.0", - "into-stream": "^5.0.0", + "into-stream": "^6.0.0", "lodash": "^4.17.4", "read-pkg-up": "^7.0.0" }, @@ -14154,6 +14190,8 @@ }, "from2": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -14293,7 +14331,9 @@ "dev": true }, "handlebars": { - "version": "4.7.6", + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -14339,7 +14379,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.8", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "html-escaper": { @@ -14429,7 +14471,9 @@ "dev": true }, "into-stream": { - "version": "5.1.1", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", "dev": true, "requires": { "from2": "^2.3.0", @@ -15341,6 +15385,8 @@ }, "p-is-promise": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true }, "p-limit": { diff --git a/package.json b/package.json index 8a4c1ef..d584863 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@alorel/rollup-plugin-copy": "^1.0.2", "@alorel/rollup-plugin-copy-pkg-json": "^1.0.3", "@alorel/rollup-plugin-dts": "^2.0.1", + "@rollup/plugin-alias": "^3.1.2", "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/pluginutils": "^4.1.0", diff --git a/projects/core/autoinit-wasm-import.js b/projects/core/autoinit-wasm-import.js index 05d6bfd..ae36280 100644 --- a/projects/core/autoinit-wasm-import.js +++ b/projects/core/autoinit-wasm-import.js @@ -1,11 +1,24 @@ -export default function autoinitWasmImport(imp, url) { - return Promise - .all([ - imp, - fetch(url) - ]) - .then(function wasmImportInitialiser([wasmModule, response]) { - return wasmModule.default(response) - .then(() => wasmModule); - }); +function createGetter(initResponse) { + const getter = {}; + Object.defineProperty(getter, '__getWasm', { + value: function __getWasm() { + return initResponse; + } + }); + + return getter; +} + +/** + * @param {Promise} imp - The dynamic import + * @param {string} url - The wasm file's URL + * @param {boolean} exposeWasm - Whether to expose the init function's response or not + */ +export default async function autoinitWasmImport(imp, url, exposeWasm) { + const [wasmModule, fetchResponse] = await Promise.all([imp, fetch(url)]); + const initResponse = await wasmModule.default(fetchResponse); + + return exposeWasm ? + Object.assign(createGetter(initResponse), wasmModule) : + wasmModule; } diff --git a/projects/core/index.spec.ts b/projects/core/index.spec.ts index abad7ed..e5e4b8e 100644 --- a/projects/core/index.spec.ts +++ b/projects/core/index.spec.ts @@ -1,34 +1,73 @@ import nodeResolve from '@rollup/plugin-node-resolve'; import {expect} from 'chai'; import {join} from 'path'; -import type {OutputChunk, RollupOutput} from 'rollup'; +import type {OutputChunk, RollupBuild, RollupOutput} from 'rollup'; import {rollup} from 'rollup'; +import type {Opts} from './index'; import {wasmBindgen} from './index'; -describe('PlaceholderTestSuite', function () { +// eslint-disable-next-line @typescript-eslint/no-var-requires +const alias = require('@rollup/plugin-alias'); + +function makeBuild(opts?: Partial): Promise { + return rollup({ + input: join(__dirname, 'test-input.js'), + plugins: [ + nodeResolve(), + alias({ + entries: { + '@alorel/rollup-plugin-wasm-bindgen-web/autoinit-wasm-import': join(__dirname, 'autoinit-wasm-import.js') + } + }), + wasmBindgen({ + ...opts, + wasmFilter: { + include: /core[\\/]fixture-output[\\/]index\.js$/, + ...opts?.wasmFilter + } + }) + ] + }); +} + +function makeBundle(build: RollupBuild): Promise { + return build.generate({ + format: 'es', + preferConst: true, + preserveModules: true + }); +} + +function makeRegex(exposeWasm: boolean): RegExp { + return new RegExp(`(autoinitWasmImport|__wasmBindgenRollupPluginDynamicImportLoader)\\(import\\(['"].+['"]\\), .+, ${exposeWasm}\\)`); +} + +function findEntryChunk(bundle: RollupOutput): OutputChunk | undefined { + return bundle.output.find((f): f is OutputChunk => f.type === 'chunk' && f.isEntry)!; +} + +describe('With wasm exposed', () => { + let bundle: RollupOutput; + let entryChunk: OutputChunk | undefined; + before(async () => { + bundle = await makeBundle(await makeBuild({exposeWasm: true})); + entryChunk = findEntryChunk(bundle); + }); + + it('Should call init factory with true as the exposeWasm argument', () => { + expect(entryChunk).to.not.eq(undefined, 'entry chunk not found'); + expect(entryChunk!.code).to.match(makeRegex(true)); + }); +}); + +describe('Without wasm exposed', function () { let bundle: RollupOutput; let entryChunk: OutputChunk | undefined; before('build', async () => { - const build = await rollup({ - input: join(__dirname, 'test-input.js'), - plugins: [ - nodeResolve(), - wasmBindgen({ - wasmFilter: { - include: /core[\\/]fixture-output[\\/]index\.js$/ - } - }) - ] - }); - - bundle = await build.generate({ - format: 'es', - preferConst: true, - preserveModules: false - }); - - entryChunk = bundle.output.find((f): f is OutputChunk => f.type === 'chunk' && f.isEntry)!; + const build = await makeBuild(); + bundle = await makeBundle(build); + entryChunk = findEntryChunk(bundle); }); it('Should have a wasm asset', () => { @@ -36,9 +75,9 @@ describe('PlaceholderTestSuite', function () { }); it('Should render entry chunk with Promise.all', () => { - expect(entryChunk).to.not.eq(undefined, 'undefined'); + expect(entryChunk).to.not.eq(undefined, 'entry chunk not found'); const c = entryChunk!.code; - expect(c).to.include('import __wasmBindgenRollupPluginDynamicImportLoader from \'@alorel/rollup-plugin-wasm-bindgen-web/autoinit-wasm-import\';'); - expect(c).to.match(/__wasmBindgenRollupPluginDynamicImportLoader\(\nimport\(['"].+['"]\),\n.+\n\)/); + expect(c).to.match(/import (autoinitWasmImport|__wasmBindgenRollupPluginDynamicImportLoader) from ['"].+autoinit-wasm-import.*['"];\n/); + expect(c).to.match(makeRegex(false)); }); }); diff --git a/projects/core/index.ts b/projects/core/index.ts index 72e7005..d3044f2 100644 --- a/projects/core/index.ts +++ b/projects/core/index.ts @@ -33,6 +33,13 @@ interface Filter { interface Opts { + /** + * When set to true, adds a __getWasm() function to the import() response which returns what the original + * init function returned + * @default false + */ + exposeWasm?: boolean; + /** * JS source files to look for dynamic imports in. * @default {include: /\.[jt]sx?$/} @@ -54,6 +61,7 @@ interface Opts { function wasmBindgenPlugin(opts: Opts): Plugin { // eslint-disable-line max-lines-per-function const { + exposeWasm = false, jsFilter = { include: /\.[jt]sx?$/ }, @@ -67,6 +75,7 @@ function wasmBindgenPlugin(opts: Opts): Plugin { // eslint-disable-line max-line const returnTransformResult: (ms: MagicString) => TransformResult = sourceMap ? (ms => ({code: ms.toString(), map: ms.generateMap()})) : (ms => ({code: ms.toString(), map: {mappings: ''}})); + const exposeWasmStr = String(exposeWasm); return { name: 'rollup-plugin-wasm-bindgen-web', @@ -115,8 +124,8 @@ function wasmBindgenPlugin(opts: Opts): Plugin { // eslint-disable-line max-line source, type: 'asset' }); - ms.prependLeft(node.start, `${Strings.ImportName}(\n`); - ms.appendRight(node.end, `,\nimport.meta.ROLLUP_FILE_URL_${assetId}\n)`); + ms.prependLeft(node.start, `${Strings.ImportName}(`); + ms.appendRight(node.end, `, import.meta.ROLLUP_FILE_URL_${assetId}, ${exposeWasmStr})`); }) );