Skip to content

Commit 41f6bc3

Browse files
committed
Add wasm support for test and SSR
1 parent 847b51b commit 41f6bc3

File tree

14 files changed

+255
-40
lines changed

14 files changed

+255
-40
lines changed

packages/vite/src/node/plugins/wasm.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,32 @@ const wasmHelper = async (opts = {}, url: string) => {
2626
}
2727
result = await WebAssembly.instantiate(bytes, opts)
2828
} else {
29-
// https://github.com/mdn/webassembly-examples/issues/5
30-
// WebAssembly.instantiateStreaming requires the server to provide the
31-
// correct MIME type for .wasm files, which unfortunately doesn't work for
32-
// a lot of static file servers, so we just work around it by getting the
33-
// raw buffer.
34-
const response = await fetch(url)
35-
const contentType = response.headers.get('Content-Type') || ''
36-
if (
37-
'instantiateStreaming' in WebAssembly &&
38-
contentType.startsWith('application/wasm')
39-
) {
40-
result = await WebAssembly.instantiateStreaming(response, opts)
41-
} else {
42-
const buffer = await response.arrayBuffer()
29+
if (typeof process !== 'undefined' && process.versions?.node) {
30+
const fs = await import('node:fs/promises')
31+
if (url.startsWith('/@fs/')) {
32+
url = url.slice(4)
33+
} else if (url.startsWith('/')) {
34+
url = url.slice(1)
35+
}
36+
const buffer = await fs.readFile(url)
4337
result = await WebAssembly.instantiate(buffer, opts)
38+
} else {
39+
// https://github.com/mdn/webassembly-examples/issues/5
40+
// WebAssembly.instantiateStreaming requires the server to provide the
41+
// correct MIME type for .wasm files, which unfortunately doesn't work for
42+
// a lot of static file servers, so we just work around it by getting the
43+
// raw buffer.
44+
const response = await fetch(url)
45+
const contentType = response.headers.get('Content-Type') || ''
46+
if (
47+
'instantiateStreaming' in WebAssembly &&
48+
contentType.startsWith('application/wasm')
49+
) {
50+
result = await WebAssembly.instantiateStreaming(response, opts)
51+
} else {
52+
const buffer = await response.arrayBuffer()
53+
result = await WebAssembly.instantiate(buffer, opts)
54+
}
4455
}
4556
}
4657
return result.instance
@@ -66,7 +77,7 @@ export const wasmHelperPlugin = (): Plugin => {
6677
return `export default ${wasmHelperCode}`
6778
}
6879

69-
const url = await fileToUrl(this, id)
80+
const url = (await fileToUrl(this, id)).split('?')[0]
7081

7182
return `
7283
import initWasm from "${wasmHelperId}"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// this is automatically detected by playground/vitestSetup.ts and will replace
2+
// the default e2e test serve behavior
3+
4+
import path from 'node:path'
5+
import kill from 'kill-port'
6+
import { hmrPorts, ports, rootDir } from '~utils'
7+
8+
export const port = ports['ssr-wasm']
9+
10+
export async function serve(): Promise<{ close(): Promise<void> }> {
11+
await kill(port)
12+
13+
const { createServer } = await import(path.resolve(rootDir, 'server.js'))
14+
const { app, vite } = await createServer(rootDir, hmrPorts['ssr-wasm'])
15+
16+
return new Promise((resolve, reject) => {
17+
try {
18+
const server = app.listen(port, () => {
19+
resolve({
20+
// for test teardown
21+
async close() {
22+
await new Promise((resolve) => {
23+
server.close(resolve)
24+
})
25+
if (vite) {
26+
await vite.close()
27+
}
28+
},
29+
})
30+
})
31+
} catch (e) {
32+
reject(e)
33+
}
34+
})
35+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect, test } from 'vitest'
2+
import { port } from './serve'
3+
import { page } from '~utils'
4+
5+
const url = `http://localhost:${port}`
6+
7+
test('should work when inlined', async () => {
8+
await page.goto(`${url}/static-light`)
9+
expect(await page.textContent('.static-light')).toMatch('42')
10+
})
11+
12+
test('should work when output', async () => {
13+
await page.goto(`${url}/static-heavy`)
14+
expect(await page.textContent('.static-heavy')).toMatch('24')
15+
})

playground/ssr-wasm/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@vitejs/test-ssr-wasm",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "node server",
8+
"serve": "NODE_ENV=production node server",
9+
"debug": "node --inspect-brk server"
10+
},
11+
"dependencies": {},
12+
"devDependencies": {
13+
"express": "^5.1.0"
14+
}
15+
}

playground/ssr-wasm/server.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import express from 'express'
2+
3+
const isTest = process.env.VITEST
4+
5+
export async function createServer(root = process.cwd(), hmrPort) {
6+
const app = express()
7+
8+
/**
9+
* @type {import('vite').ViteDevServer}
10+
*/
11+
const vite = await (
12+
await import('vite')
13+
).createServer({
14+
root,
15+
logLevel: isTest ? 'error' : 'info',
16+
server: {
17+
middlewareMode: true,
18+
watch: {
19+
// During tests we edit the files too fast and sometimes chokidar
20+
// misses change events, so enforce polling for consistency
21+
usePolling: true,
22+
interval: 100,
23+
},
24+
hmr: {
25+
port: hmrPort,
26+
},
27+
},
28+
appType: 'custom',
29+
})
30+
// use vite's connect instance as middleware
31+
app.use(vite.middlewares)
32+
33+
app.use('*all', async (req, res, next) => {
34+
try {
35+
const url = req.originalUrl
36+
const render = (await vite.ssrLoadModule('/src/app.js')).render
37+
const html = await render(url)
38+
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
39+
} catch (e) {
40+
vite && vite.ssrFixStacktrace(e)
41+
if (isTest) throw e
42+
console.log(e.stack)
43+
res.status(500).end(e.stack)
44+
}
45+
})
46+
47+
return { app, vite }
48+
}
49+
50+
if (!isTest) {
51+
createServer().then(({ app }) =>
52+
app.listen(5173, () => {
53+
console.log('http://localhost:5173')
54+
}),
55+
)
56+
}

playground/ssr-wasm/src/app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export async function render(url) {
2+
switch (url) {
3+
case '/static-light':
4+
return (await import('./static-light')).render()
5+
case '/static-heavy':
6+
return (await import('./static-heavy')).render()
7+
}
8+
}

playground/ssr-wasm/src/heavy.wasm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../wasm/heavy.wasm

playground/ssr-wasm/src/light.wasm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../wasm/light.wasm
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import heavy from './heavy.wasm?init'
2+
3+
export async function render() {
4+
let result
5+
const { exported_func } = await heavy({
6+
imports: {
7+
imported_func: (res) => (result = res),
8+
},
9+
}).then((i) => i.exports)
10+
exported_func()
11+
return `<div class="static-heavy">${result}</div>`
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import light from './light.wasm?init'
2+
3+
export async function render() {
4+
let result
5+
const { exported_func } = await light({
6+
imports: {
7+
imported_func: (res) => (result = res),
8+
},
9+
}).then((i) => i.exports)
10+
exported_func()
11+
return `<div class="static-light">${result}</div>`
12+
}

0 commit comments

Comments
 (0)