Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ export default defineConfig(async ({ command, mode }) => {
changeOrigin: true,
configure: (proxy, options) => {
// proxy will be an instance of 'http-proxy'
},
}
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions packages/playground/worker-thread-node/__tests__/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { join } = require('path')
/**
* @param {string} root
* @param {boolean} isProd
*/
exports.serve = async function serve(root, isProd) {
if (isProd) {
const { build } = require('vite')
await build({
configFile: join(root, 'vite.config.ts'),
root,
logLevel: 'silent'
})
}

return new Promise((resolve, _) => {
resolve({
close: async () => {
// no need to close anything
}
})
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { isBuild, testDir } from '../../testUtils'
import fs from 'fs-extra'
import path from 'path'

if (isBuild) {
test('response from worker', async () => {
const distDir = path.resolve(testDir, 'dist')
const { run } = require(path.join(distDir, 'main.cjs'))
expect(await run('ping')).toBe('pong')
})

test('response from inline worker', async () => {
const distDir = path.resolve(testDir, 'dist')
const { inlineWorker } = require(path.join(distDir, 'main.cjs'))
expect(await inlineWorker('ping')).toBe('this is inline node worker')
})

test('worker code generation', async () => {
const assetsDir = path.resolve(testDir, 'dist/assets')
const distDir = path.resolve(testDir, 'dist')
const files = fs.readdirSync(assetsDir)
const mainContent = fs.readFileSync(
path.resolve(distDir, 'main.cjs.js'),
'utf-8'
)

const workerFile = files.find((f) => f.includes('worker'))
const workerContent = fs.readFileSync(
path.resolve(assetsDir, workerFile),
'utf-8'
)

expect(files.length).toBe(1)

// main file worker chunk content
expect(mainContent).toMatch(`require("worker_threads")`)
expect(mainContent).toMatch(`Worker`)

// main content should contain __dirname to resolve module as relation path from main module
expect(mainContent).toMatch(`__dirname`)

// should resolve worker_treads from external dependency
expect(workerContent).toMatch(`require("worker_threads")`)

// inline nodejs worker
expect(mainContent).toMatch(`{eval:!0}`)
})
}
35 changes: 35 additions & 0 deletions packages/playground/worker-thread-node/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import MyWorker from './worker?worker'
import InlineWorker from './worker-inline?worker&inline'
import { Worker } from 'worker_threads'

export const run = async (message: string): Promise<string> => {
return new Promise((resolve, reject) => {
const worker = new MyWorker() as Worker
worker.postMessage(message)
worker.on('message', (msg) => {
worker.terminate()
resolve(msg)
})
})
}

export const inlineWorker = async (message: string): Promise<string> => {
return new Promise((resolve, reject) => {
const worker = new InlineWorker() as Worker
worker.postMessage(message)
worker.on('message', (msg) => {
worker.terminate()
resolve(msg)
})
})
}

if (require.main === module) {
Promise.all([run('ping'), inlineWorker('ping')]).then(
([chunkResponse, inlineResponse]) => {
console.log('Response from chunk worker - ', chunkResponse)
console.log('Response from inline worker - ', inlineResponse)
process.exit()
}
)
}
10 changes: 10 additions & 0 deletions packages/playground/worker-thread-node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "worker-thread-node",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "node scripts/build.js",
"build": "node scripts/build.js",
"start": "node dist/main.cjs.js"
}
}
3 changes: 3 additions & 0 deletions packages/playground/worker-thread-node/scripts/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const vite = require('vite')
const { build } = vite
build({ configFile: 'vite.config.ts' })
18 changes: 18 additions & 0 deletions packages/playground/worker-thread-node/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from 'vite'
import { builtinModules } from 'module'

export default defineConfig({
build: {
target: 'node16',
outDir: 'dist',
lib: {
entry: 'main.ts',
formats: ['cjs'],
fileName: 'main'
},
rollupOptions: {
external: [...builtinModules]
},
emptyOutDir: true
}
})
5 changes: 5 additions & 0 deletions packages/playground/worker-thread-node/worker-inline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { parentPort } from 'worker_threads'

parentPort.on('message', () => {
parentPort.postMessage('this is inline node worker')
})
5 changes: 5 additions & 0 deletions packages/playground/worker-thread-node/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { parentPort } from 'worker_threads'

parentPort.on('message', (message) => {
parentPort.postMessage('pong')
})
6 changes: 4 additions & 2 deletions packages/vite/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,17 @@ declare module '*.webmanifest' {

// web worker
declare module '*?worker' {
import { Worker as NodeWorker } from 'worker_threads'
const workerConstructor: {
new (): Worker
new (): Worker | NodeWorker
}
export default workerConstructor
}

declare module '*?worker&inline' {
import { Worker as NodeWorker } from 'worker_threads'
const workerConstructor: {
new (): Worker
new (): Worker | NodeWorker
}
export default workerConstructor
}
Expand Down
45 changes: 39 additions & 6 deletions packages/vite/src/node/plugins/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Plugin } from '../plugin'
import { resolvePlugins } from '../plugins'
import { parse as parseUrl, URLSearchParams } from 'url'
import { fileToUrl, getAssetHash } from './asset'
import { cleanUrl, injectQuery } from '../utils'
import { cleanUrl, injectQuery, isTargetNode } from '../utils'
import Rollup from 'rollup'
import { ENV_PUBLIC_PATH } from '../constants'
import path from 'path'
Expand Down Expand Up @@ -51,25 +51,46 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
}

let url: string
const isNode = isTargetNode(config?.build?.target)

if (isBuild) {
// bundle the file as entry to support imports
const rollup = require('rollup') as typeof Rollup
const bundle = await rollup.rollup({
input: cleanUrl(id),
...config?.build?.rollupOptions,
plugins: await resolvePlugins({ ...config }, [], [], [])
})

let code: string
try {
const { output } = await bundle.generate({
format: 'iife',
sourcemap: config.build.sourcemap
})
code = output[0].code
if (isNode) {
const { output } = await bundle.generate({
format: 'cjs',
sourcemap: config.build.sourcemap
})
code = output[0].code
} else {
const { output } = await bundle.generate({
format: 'iife',
sourcemap: config.build.sourcemap
})
code = output[0].code
}
} finally {
await bundle.close()
}
const content = Buffer.from(code)
if (query.inline != null) {
if (isNode) {
return `
import { Worker } from "worker_threads" \n
import { join } from "path" \n
export default function WorkerWrapper() {
return new Worker(\'${content.toString().trim()}', { eval: true })
}
`
}
// inline as blob data url
return `const blob = new Blob([atob(\"${content.toString(
'base64'
Expand Down Expand Up @@ -104,6 +125,18 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
query.sharedworker != null ? 'SharedWorker' : 'Worker'
const workerOptions = { type: 'module' }

if (isNode) {
return `
import { Worker } from "worker_threads" \n
import { join } from "path" \n
export default function WorkerWrapper() {
return new Worker(join(__dirname, ${JSON.stringify(
url
)}), ${JSON.stringify(workerOptions, null, 2)})
}
`
}

return `export default function WorkerWrapper() {
return new ${workerConstructor}(${JSON.stringify(
url
Expand Down
16 changes: 16 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,19 @@ export function resolveHostname(

return { host, name }
}

export function isTargetNode(target: string | false | string[]): boolean {
if (!target) {
return false
}

if (typeof target === 'string') {
return target.includes('node')
}

if (Array.isArray(target)) {
return Boolean(target.filter((f) => f.includes('node')).length)
}

return false
}