webworker 实践:外部依赖引入和打包问题

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

相关推荐
亭台烟雨中13 分钟前
【前端记事】关于electron的入门使用
前端·javascript·electron
泯泷27 分钟前
「译」解析 JavaScript 中的循环依赖
前端·javascript·架构
抹茶san30 分钟前
前端实战:从 0 开始搭建 pnpm 单一仓库(1)
前端·架构
Senar1 小时前
Web端选择本地文件的几种方式
前端·javascript·html
烛阴1 小时前
UV Coordinates & Uniforms -- OpenGL UV坐标和Uniform变量
前端·webgl
姑苏洛言1 小时前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
烛阴1 小时前
JavaScript 的 8 大“阴间陷阱”,你绝对踩过!99% 程序员崩溃瞬间
前端·javascript·面试
lh_12542 小时前
ECharts 地图开发入门
前端·javascript·echarts