Skip to content

Commit

Permalink
feat(wasm): new wasm plugin (.wasm?init) (#8219)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed May 20, 2022
1 parent a0ee4ff commit 75c3bf6
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 19 deletions.
13 changes: 9 additions & 4 deletions docs/guide/features.md
Expand Up @@ -435,13 +435,13 @@ Note that variables only represent file names one level deep. If `file` is `'foo

## WebAssembly

Pre-compiled `.wasm` files can be directly imported - the default export will be an initialization function that returns a Promise of the exports object of the wasm instance:
Pre-compiled `.wasm` files can be imported with `?init` - the default export will be an initialization function that returns a Promise of the wasm instance:

```js
import init from './example.wasm'
import init from './example.wasm?init'

init().then((exports) => {
exports.test()
init().then((instance) => {
instance.exports.test()
})
```

Expand All @@ -461,6 +461,11 @@ init({

In the production build, `.wasm` files smaller than `assetInlineLimit` will be inlined as base64 strings. Otherwise, they will be copied to the dist directory as an asset and fetched on-demand.

::: warning
[ES Module Integration Proposal for WebAssembly](https://github.com/WebAssembly/esm-integration) is not currently supported.
Use [`vite-plugin-wasm`](https://github.com/Menci/vite-plugin-wasm) or other community plugins to handle this.
:::

## Web Workers

### Import with Constructors
Expand Down
6 changes: 4 additions & 2 deletions packages/vite/client.d.ts
Expand Up @@ -152,8 +152,10 @@ declare module '*.otf' {
}

// other
declare module '*.wasm' {
const initWasm: (options: WebAssembly.Imports) => Promise<WebAssembly.Exports>
declare module '*.wasm?init' {
const initWasm: (
options: WebAssembly.Imports
) => Promise<WebAssembly.Instance>
export default initWasm
}
declare module '*.webmanifest' {
Expand Down
1 change: 0 additions & 1 deletion packages/vite/src/node/constants.ts
Expand Up @@ -81,7 +81,6 @@ export const KNOWN_ASSET_TYPES = [
'otf',

// other
'wasm',
'webmanifest',
'pdf',
'txt'
Expand Down
5 changes: 3 additions & 2 deletions packages/vite/src/node/plugins/index.ts
Expand Up @@ -10,7 +10,7 @@ import { cssPlugin, cssPostPlugin } from './css'
import { assetPlugin } from './asset'
import { clientInjectionsPlugin } from './clientInjections'
import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html'
import { wasmPlugin } from './wasm'
import { wasmFallbackPlugin, wasmHelperPlugin } from './wasm'
import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill'
import { webWorkerPlugin } from './worker'
import { preAliasPlugin } from './preAlias'
Expand Down Expand Up @@ -64,10 +64,11 @@ export async function resolvePlugins(
},
isBuild
),
wasmPlugin(config),
wasmHelperPlugin(config),
webWorkerPlugin(config),
assetPlugin(config),
...normalPlugins,
wasmFallbackPlugin(),
definePlugin(config),
cssPostPlugin(config),
config.build.ssr ? ssrRequireHookPlugin(config) : null,
Expand Down
27 changes: 23 additions & 4 deletions packages/vite/src/node/plugins/wasm.ts
Expand Up @@ -37,14 +37,14 @@ const wasmHelper = async (opts = {}, url: string) => {
result = await WebAssembly.instantiate(buffer, opts)
}
}
return result.instance.exports
return result.instance
}

const wasmHelperCode = wasmHelper.toString()

export const wasmPlugin = (config: ResolvedConfig): Plugin => {
export const wasmHelperPlugin = (config: ResolvedConfig): Plugin => {
return {
name: 'vite:wasm',
name: 'vite:wasm-helper',

resolveId(id) {
if (id === wasmHelperId) {
Expand All @@ -57,7 +57,7 @@ export const wasmPlugin = (config: ResolvedConfig): Plugin => {
return `export default ${wasmHelperCode}`
}

if (!id.endsWith('.wasm')) {
if (!id.endsWith('.wasm?init')) {
return
}

Expand All @@ -70,3 +70,22 @@ export default opts => initWasm(opts, ${JSON.stringify(url)})
}
}
}

export const wasmFallbackPlugin = (): Plugin => {
return {
name: 'vite:wasm-fallback',

async load(id) {
if (!id.endsWith('.wasm')) {
return
}

throw new Error(
'"ESM integration proposal for Wasm" is not supported currently. ' +
'Use vite-plugin-wasm or other community plugins to handle this. ' +
'Alternatively, you can use `.wasm?init` or `.wasm?url`. ' +
'See https://vitejs.dev/guide/features.html#webassembly for more details.'
)
}
}
}
16 changes: 15 additions & 1 deletion playground/wasm/__tests__/wasm.spec.ts
@@ -1,4 +1,4 @@
import { page, untilUpdated } from '~utils'
import { isBuild, page, untilUpdated } from '~utils'

test('should work when inlined', async () => {
await page.click('.inline-wasm .run')
Expand All @@ -10,6 +10,20 @@ test('should work when output', async () => {
await untilUpdated(() => page.textContent('.output-wasm .result'), '24')
})

test('init function returns WebAssembly.Instance', async () => {
await page.click('.init-returns-instance .run')
await untilUpdated(
() => page.textContent('.init-returns-instance .result'),
'true'
)
})

test('?url', async () => {
expect(await page.textContent('.url')).toMatch(
isBuild ? 'data:application/wasm' : '/light.wasm'
)
})

test('should work when wasm in worker', async () => {
await untilUpdated(() => page.textContent('.worker-wasm .result'), '3')
})
34 changes: 31 additions & 3 deletions playground/wasm/index.html
Expand Up @@ -12,14 +12,25 @@ <h3>When wasm is output, result should be 24</h3>
<span class="result"></span>
</div>

<div class="init-returns-instance">
<h3>init function returns WebAssembly.Instance</h3>
<button class="run">Click to run</button>
<span class="result"></span>
</div>

<div>
<h3>Importing as URL</h3>
<span class="url"></span>
</div>

<div class="worker-wasm">
<h3>worker wasm</h3>
<span class="result"></span>
</div>

<script type="module">
import light from './light.wasm'
import heavy from './heavy.wasm'
import light from './light.wasm?init'
import heavy from './heavy.wasm?init'
import myWorker from './worker?worker'

const w = new myWorker()
Expand All @@ -32,7 +43,7 @@ <h3>worker wasm</h3>
imports: {
imported_func: (res) => (resultElement.textContent = res)
}
})
}).then((i) => i.exports)
exported_func()
}

Expand All @@ -51,4 +62,21 @@ <h3>worker wasm</h3>
.addEventListener('click', async () =>
testWasm(heavy, document.querySelector('.output-wasm .result'))
)

document
.querySelector('.init-returns-instance .run')
.addEventListener('click', async () => {
const res = await light({
imports: {
imported_func: (res) => (resultElement.textContent = res)
}
})
text(
'.init-returns-instance .result',
res instanceof WebAssembly.Instance
)
})

import lightUrl from './light.wasm?url'
text('.url', lightUrl)
</script>
4 changes: 2 additions & 2 deletions playground/wasm/worker.js
@@ -1,5 +1,5 @@
import init from './add.wasm'
init().then((exports) => {
import init from './add.wasm?init'
init().then(({ exports }) => {
// eslint-disable-next-line no-undef
self.postMessage({ result: exports.add(1, 2) })
})

0 comments on commit 75c3bf6

Please sign in to comment.