feat: support for node worker (#51)

This commit is contained in:
alex8088 2022-11-08 00:36:22 +08:00
parent bf0c83835a
commit 28d069c453
6 changed files with 107 additions and 2 deletions

5
node.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
// node worker
declare module '*?nodeWorker' {
import { Worker, WorkerOptions } from 'node:worker_threads'
export default function (options: WorkerOptions): Worker
}

View file

@ -9,7 +9,8 @@
}, },
"files": [ "files": [
"bin", "bin",
"dist" "dist",
"node.d.ts"
], ],
"engines": { "engines": {
"node": ">=12.2.0" "node": ">=12.2.0"
@ -83,6 +84,7 @@
"@babel/plugin-transform-arrow-functions": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.18.6",
"cac": "^6.7.14", "cac": "^6.7.14",
"esbuild": "^0.15.12", "esbuild": "^0.15.12",
"magic-string": "^0.26.7",
"picocolors": "^1.0.0" "picocolors": "^1.0.0"
} }
} }

View file

@ -17,6 +17,7 @@ specifiers:
eslint-plugin-prettier: ^4.2.1 eslint-plugin-prettier: ^4.2.1
fs-extra: ^10.1.0 fs-extra: ^10.1.0
lint-staged: ^13.0.3 lint-staged: ^13.0.3
magic-string: ^0.26.7
picocolors: ^1.0.0 picocolors: ^1.0.0
prettier: ^2.7.1 prettier: ^2.7.1
rollup: ^2.79.1 rollup: ^2.79.1
@ -30,6 +31,7 @@ dependencies:
'@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.6 '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.6
cac: 6.7.14 cac: 6.7.14
esbuild: 0.15.12 esbuild: 0.15.12
magic-string: 0.26.7
picocolors: 1.0.0 picocolors: 1.0.0
devDependencies: devDependencies:
@ -1916,6 +1918,13 @@ packages:
yallist: 4.0.0 yallist: 4.0.0
dev: true dev: true
/magic-string/0.26.7:
resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==}
engines: {node: '>=12'}
dependencies:
sourcemap-codec: 1.4.8
dev: false
/merge-stream/2.0.0: /merge-stream/2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true dev: true
@ -2277,6 +2286,10 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/sourcemap-codec/1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
dev: false
/sprintf-js/1.0.3: /sprintf-js/1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
dev: true dev: true

View file

@ -16,6 +16,7 @@ import {
import { build } from 'esbuild' import { build } from 'esbuild'
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugin' import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugin'
import workerPlugin from './plugins/worker'
import { isObject, dynamicImport } from './utils' import { isObject, dynamicImport } from './utils'
export { defineConfig as defineViteConfig } from 'vite' export { defineConfig as defineViteConfig } from 'vite'
@ -129,7 +130,7 @@ export async function resolveConfig(
resetOutDir(mainViteConfig, outDir, 'main') resetOutDir(mainViteConfig, outDir, 'main')
} }
mergePlugins(mainViteConfig, electronMainVitePlugin({ root })) mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), workerPlugin()])
loadResult.config.main = mainViteConfig loadResult.config.main = mainViteConfig
loadResult.config.main.configFile = false loadResult.config.main.configFile = false

69
src/plugins/worker.ts Normal file
View file

@ -0,0 +1,69 @@
import path from 'node:path'
import type { Plugin } from 'vite'
import type { SourceMapInput } from 'rollup'
import MagicString from 'magic-string'
import { cleanUrl, parseRequest } from '../utils'
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([a-z\d]{8})__/g
/**
* Resolve `?nodeWorker` import and automatically generate `Worker` wrapper.
*/
export default function workerPlugin(): Plugin {
let sourcemap: boolean | 'inline' | 'hidden' = false
return {
name: 'vite:node-worker',
apply: 'build',
enforce: 'pre',
configResolved(config): void {
sourcemap = config.build.sourcemap
},
resolveId(id, importer): string | void {
const query = parseRequest(id)
if (query && typeof query.nodeWorker === 'string') {
return id + `&importer=${importer}`
}
},
load(id): string | void {
const query = parseRequest(id)
if (query && typeof query.nodeWorker === 'string' && typeof query.importer === 'string') {
const cleanPath = cleanUrl(id)
const hash = this.emitFile({
type: 'chunk',
id: cleanPath,
importer: query.importer
})
const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__`
return `
import { Worker } from 'node:worker_threads';
export default function (options) { return new Worker(require.resolve(${assetRefId}), options); }`
}
},
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {
if (code.match(nodeWorkerAssetUrlRE)) {
let match: RegExpExecArray | null
const s = new MagicString(code)
while ((match = nodeWorkerAssetUrlRE.exec(code))) {
const [full, hash] = match
const filename = this.getFileName(hash)
let outputFilepath = path.posix.relative(path.dirname(chunk.fileName), filename)
if (!outputFilepath.startsWith('.')) {
outputFilepath = './' + outputFilepath
}
const replacement = JSON.stringify(outputFilepath)
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
})
}
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: true }) : null
}
}
return null
}
}
}

View file

@ -1,3 +1,5 @@
import { URL, URLSearchParams } from 'node:url'
export function isObject(value: unknown): value is Record<string, unknown> { export function isObject(value: unknown): value is Record<string, unknown> {
return Object.prototype.toString.call(value) === '[object Object]' return Object.prototype.toString.call(value) === '[object Object]'
} }
@ -9,3 +11,16 @@ export const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000
export function resolveHostname(optionsHost: string | boolean | undefined): string { export function resolveHostname(optionsHost: string | boolean | undefined): string {
return typeof optionsHost === 'string' && !wildcardHosts.has(optionsHost) ? optionsHost : 'localhost' return typeof optionsHost === 'string' && !wildcardHosts.has(optionsHost) ? optionsHost : 'localhost'
} }
export const queryRE = /\?.*$/s
export const hashRE = /#.*$/s
export const cleanUrl = (url: string): string => url.replace(hashRE, '').replace(queryRE, '')
export function parseRequest(id: string): Record<string, string> | null {
const { search } = new URL(id, 'file:')
if (!search) {
return null
}
return Object.fromEntries(new URLSearchParams(search))
}