Web Worker实践:解决外部依赖引入与打包问题

开发工具 潘老师 1小时前 3 ℃ (0) 扫码查看

Web Worker是一项非常实用的技术,它能够在后台运行任务,避免阻塞浏览器的主线程。当项目中存在一些比较耗时的计算任务时,为了防止页面出现卡顿甚至卡死的情况,Web Worker就派上用场了。接下来,我们通过一个具体案例,深入探讨Web Worker实践过程中遇到的外部依赖引入和打包相关问题。

一、Web Worker应用案例:JSON Diff比对

本次案例使用create-react-app搭建一个demo项目,利用Web Worker在后台进行JSON数据的差异比对(JSON Diff)。

(一)核心代码展示

  1. 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事件监听函数会在接收到主线程发送的消息时触发,当判断消息的actionjsonDiff时,从消息的payload中获取需要比对的两个JSON数据json1json2 ,使用Differ实例进行比对,最后将比对结果通过postMessage发送回主线程。

  1. 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比对。通过useStateuseEffect钩子函数,监听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在前端开发中的优势。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/tool/18033.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】