我们在web端用js计算一些较为复杂耗时的运算时,常常需要用到Web Worker技术.他能够在子线程中运算,最终将结果返回主线程,使得主线程不会卡死.特别是3d场景游戏中,Web Worker使用会不可或缺.
在公司项目里,我有负责的canvas引擎开发中,经常会碰到一些需要用worker多线程的复杂运算场景.然而因为是引擎库,我更希望的是不将worker单独编译成一个js文件引入.
我这边的处理是将worker的代码变成字符串,然后通过Blob动态引入,具体代码如下:
const script = `...` // worker 代码
const workerBlob = new Blob([script],{type:'text/javascript'})
const url = URL.createObjectURL(workerBlob)
const worker = new Worker(url)
return worker
到这里我们就可以把worker的代码打包进其他js文件,而不是单独的worker文件了,然而这样做最大的问题是些代码很不方便,并且不好调试(worker的代码往往涉及复杂的数学公式和逻辑运算)
我这边的做法是本地开发时,使用单独的worker js文件开发,只有在打包的时候,先将代码预处理成字符串,然后再通过Blob动态引入,关键代码如下:
export function getWorker(fileName:string){
if ( IS_DEV ) {
// 调试
const FileWorker = require(`worker-loader!../worker/${fileName}.worker.js`)
const worker = new FileWorker.default()
return worker as Worker
} else {
// 打包
const scriptWorker = require(`../worker-lib/${fileName}.worker.js`)
const script = scriptWorker.default
// ... 跟上面的Blob方式一致
}
}
预处理的node程序参考如下:
generate-worker.js
const inputPath = './src/lib/worker'
const outputPath = './src/lib/worker-lib'
var fs = require('fs')
var path = require('path')
var filePath = path.resolve(__dirname,inputPath)
var copyFilePath = path.resolve(__dirname,outputPath)
fileDisplay(filePath,copyFilePath)
function fileDisplay(filePath,copyFilePath){
fs.mkdirSync(copyFilePath)
const fileList = fs.readdirSync(filePath)
for (let i = 0;i< fileList.length;i++) {
const fileName = fileList[i]
const fileDir = path.join(filePath,fileName)
const text = fs.readFileSync(fileDir,'utf-8')
const scriptText = `const script = \`${text}\`; export default script`
const writeFilePath = path.join(copyFilePath,fileName)
fs.writeFileSync(writeFilePath,scriptText,{ encoding:'utf-8' })
}
console.log('generate worker over')
}
然后再package.json中添加generate命令,用来生成worker字符串:
"generate":"rimraf ./src/lib/worker && node generate-worker.js"
然后在打包的命令前面运行generate,例如:npm run generate && npm run build
现在解决了在worker里调试js代码的问题,但是你要非常注意的使用js代码,因为这块js代码是不会经过babel编译,而是直接输出,很有可能调试的时候好好的,但是打包出来有问题(使用了浏览器还不支持的语法)
我这边给出的解决方案是直接用ts写worker代码,然后通过tsc命令将ts文件编译成js文件后,再调用我们写的generate-worker.js将js代码转成字符串,最后调用打包命令,打包已经转成字符串的代码.
我自己的worker目录管理:
ts写的worker文件位置: src/lib/worker
tsc编译成js文件的目录: src/lib/worker-template-lib
generate生成worker字符串的目录:src/lib/worker-lib
tsc命令: "rimraf ./src/lib/worker-template-lib && tsc --outDir ./src/lib/worker-template-lib src/lib/worker/* "
worker.ts 代码修改: const FileWorker = require(
worker-loader!../worker/${fileName}.worker.ts)
generate-worker.js里面inputPath修改: const inputPath = './src/lib/worker-template-lib'
到这一步应该差不多能完美解决调试和打包 worker的需求了