web worker可以在后台运行,不阻塞浏览器主线程。当项目中有些计算比较耗时,为防止页面卡死,就可以使用worker。
案例:使用web worker在后台进行json diff比对
demo项目用create-react-app生成
worker.js
ini
import { Differ } from 'json-diff-kit';
onmessage = function(event) {
if(event.data?.action==='jsonDiff'){
const { json1, json2 } = event.data.payload;
const differ = new Differ();
const diff = differ.diff(json1, json2);
postMessage({action:'jsonDiff',payload:diff});
}
};
App.js
ini
import './App.css';
import { useEffect, useState } from 'react';
import { Viewer } from 'json-diff-kit';
import 'json-diff-kit/dist/viewer.css';
import json1 from './test1.json'
import json2 from './test2.json'
const worker = new Worker('./worker.js');
worker.postMessage({action:"jsonDiff",payload:{json1, json2}});
function App() {
const [diff,setDiff] = useState(null)
useEffect(()=>{
worker.onmessage = function(event) {
if(event.data?.action==='jsonDiff'){
const diff = event.data.payload;
setDiff(diff)
}
};
},[])
return (
<div className="App">
{diff? <Viewer
diff={ diff } // required
indent={ 2 } // default `2`
lineNumbers={ true } // default `false`
highlightInlineDiff={ true } // default `false`
hideUnchangedLines={ true }
inlineDiffOptions={ {
mode: 'word', // default `"char"`, but `"word"` may be more useful
wordSeparator: ' ', // default `""`, but `" "` is more useful for sentences
} }
/>:'loading...'}
</div>
);
}
export default App;
效果如下:

不过本文并非介绍json比对方法,而是想通过该案例引入web worker实践中的问题:worker.js 404报错和第三方依赖的加载。
worker.js 404
ini
const worker = new Worker('./worker.js');
web worker从当前服务根目录下拉取worker.js,如果不做配置,运行的时候请求worker.js一定会报404。因此需要对worker.js拆包为一个独立的文件。
利用webpack 多入口打包
最简单的方式是利用webpack多入口打包的方式,在entry中增加一个worker的入口:
webpack.config.js
css
{
entry: {
main:paths.appIndexJs,
worker: path.resolve(__dirname,'../src/worker.js'),
},
output:{
filename: (pathData)=>{
return pathData.chunk.name === 'worker' ? '[name].js' : (isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js');
},
}
}
需要注意的是,输出文件,worker.js需要保持文件名,而其他文件可能需要带上hash,因此还需要修改output的filename。
另外HtmlWebpackPlugin要加属性 excludeChunks: ["worker"],
利用worker-loader
webpack.config.js
css
module.exports = {
module: {
rules: [
{
test: /.worker.js$/,
use: { loader: "worker-loader" },
},
],
},
};
worker.js引入方式也需要改:
javascript
import Worker from "./worker.js";
const worker = new Worker();
使用worker-loader后会打出一个bundle.worker.js的文件,和多入口打包的类似

将worker.js转为blob
在组件中,没法使用webpack多入口打包方案,但可以将worker.js转为blob。
比如有一个解析excel的worker:
ini
const workercode = () => {
importScripts('https://xforceplus-static-website.oss-cn-hangzhou.aliyuncs.com/public/xlsx/xlsx.full.min.js');
try {
self.readExcel = function readExcel({ file, content }, callback) {
XLSX.readFile....
};
self.onmessage = function (e) {
const data = e.data;
if (data.action === 'parseExcel') {
const { file, content } = data.payload;
self.readExcel({ file, content }, (result) => {
self.postMessage({ type: 'parseExcel', payload: result });
});
}
};
} catch (error) {
window.console.log(error);
}
};
let code = workercode.toString();
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
const blob = new Blob([code], { type: 'application/javascript' });
const worker_script = URL.createObjectURL(blob);
export default worker_script;
使用该worker:
javascript
import worker_script from '../../utils/worker/parseExcelWorker';
const worker = new Worker(worker_script);
这种方法适合比较简单的worker
在rollup中使用worker
rollup打包组件,需要将worker单独打包出独立文件,可以借助@surma/rollup-plugin-off-main-thread 插件进行打包。
ini
import workerURL from "omt:./worker.js";
const worker = new Worker(workerURL, { name: "main-worker" });
export default function JsonDiff(json1,json2,callback){
worker.postMessage({action:"jsonDiff",payload:{json1, json2}});
worker.onmessage = function(event) {
if(event.data?.action==='jsonDiff'){
const diff = event.data.payload;
callback(diff)
}
};
}
ini
import { Differ } from 'json-diff-kit';
onmessage = function(event) {
if(event.data?.action==='jsonDiff'){
const { json1, json2 } = event.data.payload;
const differ = new Differ();
const diff = differ.diff(json1, json2);
postMessage({action:'jsonDiff',payload:diff});
}
};
打包后的产出可以看到单独的worker文件

其中index编译后产物如下:
ini
var workerURL = "worker-6aa12ca3.js";
const worker = new Worker(workerURL, { name: "main-worker" });
function JsonDiff(json1,json2,callback){
worker.postMessage({action:"jsonDiff",payload:{json1, json2}});
worker.onmessage = function(event) {
if(event.data?.action==='jsonDiff'){
const diff = event.data.payload;
callback(diff);
}
};
}
export default JsonDiff;
但使用该rollup打包的组件,仍需要按照上文多入口打包方式将worker-6aa12ca3.js单独处理。
如果想要让lib用户无感使用,可以参考上文的blob方式。
加载第三方依赖
虽然worker支持通过import加载ES Module:
worker.js
import _ from 'https://cdn.jsdelivr.net/npm/[email protected]/lodash.js';
self.onmessage = (e) => {
const result = _.shuffle(e.data);
postMessage(result);
};
如果该ES Module还import了其他模块,所有模块都会一一请求并加载进来。
实践中很少采用该方式。在上面案例中,分别使用了npm包和importScripts加载第三方依赖。
加载npm包
如果使用webpack多入口打包,可以看到打包后的worker.js包含了该npm包的代码:
除了最后两行,其余都是json-diff-kit中的。
importScripts
importScripts()
可以同步地引入一个或多个脚本文件:
rust
importScripts('https://xforceplus-static-website.oss-cn-hangzhou.aliyuncs.com/public/xlsx/xlsx.full.min.js');
如上代码引入后,可以使用全局对象XLSX