Web Worker实践:解决外部依赖引入与打包问题
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(
, { 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在前端开发中的优势。