Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wasm): new wasm plugin (.wasm?init) #8219

Merged
merged 6 commits into from May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
29 changes: 25 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,24 @@ 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.'
].join('\n')
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
}
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) })
})