章
目
录
Web Worker是一项非常实用的技术,它能够在后台运行任务,避免阻塞浏览器的主线程。当项目中存在一些比较耗时的计算任务时,为了防止页面出现卡顿甚至卡死的情况,Web Worker就派上用场了。接下来,我们通过一个具体案例,深入探讨Web Worker实践过程中遇到的外部依赖引入和打包相关问题。
一、Web Worker应用案例:JSON Diff比对
本次案例使用create-react-app
搭建一个demo项目,利用Web Worker在后台进行JSON数据的差异比对(JSON Diff)。
(一)核心代码展示
- worker.js:这个文件负责在Web Worker中执行具体的JSON Diff比对操作。
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});
}
};
在这段代码中,首先引入了json-diff-kit
库中的Differ
工具,用于处理JSON差异比对。onmessage
事件监听函数会在接收到主线程发送的消息时触发,当判断消息的action
为jsonDiff
时,从消息的payload
中获取需要比对的两个JSON数据json1
和json2
,使用Differ
实例进行比对,最后将比对结果通过postMessage
发送回主线程。
- App.js:作为React应用的主组件,负责与Web Worker进行交互,并展示比对结果。
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;
在App.js
中,引入了相关的CSS样式和JSON数据文件,创建了一个Web Worker实例,并向其发送包含两个JSON数据的消息,请求进行JSON Diff比对。通过useState
和useEffect
钩子函数,监听Web Worker返回的消息,当接收到比对结果后更新组件状态,最后根据状态在页面上展示比对结果或者加载提示。
(二)案例效果展示
经过实际运行,该案例能够成功比对两个JSON数据,并展示出差异部分,效果类似如下表格:
不过,本文重点不是介绍JSON比对的具体方法,而是通过这个案例来探讨Web Worker实践中遇到的两个关键问题:worker.js
的404报错以及第三方依赖的加载。
二、解决worker.js 404报错问题
在Web Worker的使用过程中,经常会遇到worker.js
报404错误的情况。这是因为Web Worker默认从当前服务的根目录下拉取worker.js
文件,如果没有进行额外配置,运行时就会找不到该文件,从而报错。下面介绍几种解决这个问题的方法。
(一)利用webpack多入口打包
这是一种较为常用的解决方式。在webpack.config.js
文件中,通过配置多入口,将worker.js
作为一个独立的入口:
{
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');
},
}
}
在这段配置中,entry
部分新增了worker
入口,指定worker.js
的路径。output
部分对输出文件名进行了特殊处理,确保worker.js
保持原名,而其他文件根据开发环境和生产环境的不同,采用不同的命名规则(生产环境带哈希值,开发环境为bundle.js
)。同时,还需要在HtmlWebpackPlugin
中添加属性excludeChunks: ["worker"]
,防止将worker.js
错误地引入HTML文件。
(二)使用worker-loader
在webpack.config.js
中配置worker-loader
,也能解决worker.js
的加载问题:
module.exports = {
module: {
rules: [
{
test: /.worker.js$/,
use: { loader: "worker-loader" },
},
],
},
};
使用worker-loader
后,worker.js
的引入方式也需要相应改变:
import Worker from "./worker.js";
const worker = new Worker();
这种方式会生成一个名为bundle.worker.js
的文件,和多入口打包的效果类似,都能让Web Worker正确加载worker.js
文件。
(三)将worker.js转为blob
在某些组件场景下,可能无法使用webpack多入口打包方案,这时可以将worker.js
转为blob对象。例如,有一个解析Excel的worker
,代码如下:
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
时,引入方式如下:
import worker_script from '../../utils/worker/parseExcelWorker';
const worker = new Worker(worker_script);
这种方法比较适合结构相对简单的worker
。
(四)在rollup中使用worker
如果使用rollup打包组件,想要将worker
单独打包出独立文件,可以借助@surma/rollup-plugin-off-main-thread
插件。相关代码如下:
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)
}
};
}
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
文件,例如worker-6aa12ca3.js
。不过,使用这种方式打包的组件,在实际使用时,仍需要参考上文的多入口打包方式,对生成的worker
文件进行单独处理。如果希望使用组件的开发者无感使用,可以参考将worker.js
转为blob的方式。
三、Web Worker中第三方依赖的加载方式
在Web Worker中加载第三方依赖,有多种方式可供选择,不同方式各有特点。
(一)加载npm包
如果采用webpack多入口打包的方式,打包后的worker.js
会包含所使用的npm包代码。以json-diff-kit
为例,打包后的worker.js
除了最后两行,其余部分大多是json-diff-kit
中的代码。这种方式的优点是方便管理依赖,缺点是可能会使worker.js
文件体积增大。
(二)importScripts
importScripts()
函数可以同步引入一个或多个脚本文件,例如:
importScripts('https://xforceplus-static-website.oss-cn-hangzhou.aliyuncs.com/public/xlsx/xlsx.full.min.js');
引入之后,就可以在Web Worker中使用对应的全局对象(如这里的XLSX
)。这种方式的好处是引入的脚本文件可以独立管理,不会增加worker.js
的体积,但可能会增加网络请求次数。
虽然Web Worker支持通过import
加载ES Module,例如:
import _ from 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js';
self.onmessage = (e) => {
const result = _.shuffle(e.data);
postMessage(result);
};
但是,如果该ES Module还引入了其他模块,所有相关模块都会一一请求并加载进来,这种方式在实际项目中很少使用,因为会带来较多的网络开销和加载复杂性。
通过上述对Web Worker实践中外部依赖引入和打包问题的详细探讨,希望能帮助大家在使用Web Worker时,顺利地解决遇到的各种问题,充分发挥Web Worker在前端开发中的优势。