如何使用worker-loader通过webworkers创建打字稿库

wwi*_*ski 3 javascript web-worker typescript webpack worker-loader

我尝试使用网络工作者创建打字稿库。当我使用 webpack-dev-server 测试我的代码时,一切看起来都不错,所有文件都找到了,但是当我让 npm run build 并尝试在另一个本地项目(npm install /local/path)中使用 lib 时,我GET http://localhost:8080/X.worker.js 在浏览器控制台中看到。

webpack.config.js:

const path = require('path');

module.exports = {
    devtool: 'inline-source-map',
    entry: {
        'mylib': './src/index.ts',
        'mylib.min': './src/index.ts',
    },
    output: {
        path: path.resolve(__dirname, '_bundles'),
        filename: '[name].js',
        libraryTarget: 'umd',
        library: 'mylib',
        umdNamedDefine: true
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js']
    },
    optimization: {
        minimize: true
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'awesome-typescript-loader',
                exclude: /node_modules/,
                query: {
                    declaration: false,
                }
            },
            {
                test: /\.worker\.js$/,
                use: {
                    loader: "worker-loader"
                }
            },
        ]
    }
};
Run Code Online (Sandbox Code Playgroud)

tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "module": "es6",
        "lib": [
            "webworker",
            "es2015",
            "dom"
        ],
        "moduleResolution": "node",
        "sourceMap": true,
        "strict": true,
        "alwaysStrict": true,
        "outDir": "lib",
        "resolveJsonModule": true,
        "declaration": true,
        "skipLibCheck": true,
        "allowJs": true
    },
    "include": [
        "**/*.ts",
        "**/*.tsx"
    ],
    "exclude": [
        "node_modules",
        "lib",
    ]
}
Run Code Online (Sandbox Code Playgroud)

包.json

{
  "name": "mylib",
  "version": "1.0.0",
  "description": "",
  "main": "_bundles/mylib.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack",
    "build": "rm -rf ./lib && tsc",
    "serve": "webpack-dev-server",
    "clean": "rm -rf _bundles lib lib-esm",
    "newbuild": "npm run clean && tsc && tsc -m es6 --outDir lib-esm && webpack"
  },
  "repository": {
    "type": "git",
    "url": "..."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "..."
  },
  "homepage": "...e",
  "devDependencies": {
    "prettier": "^2.1.2",
    "tslint": "^6.1.3",
    "tslint-config-prettier": "^1.18.0",
    "worker-loader": "^3.0.5"
  },
  "dependencies": {
     ...
  }
}
Run Code Online (Sandbox Code Playgroud)

我如何导入工人的示例:

import X from "worker-loader!./X";
Run Code Online (Sandbox Code Playgroud)

Set*_*ske 5

几个月前我发现自己处于完全相同的情况。我找到了一个适合我的解决方案,但首先让我们讨论一下为什么会发生这种情况。

问题:

这里有3层。

  1. 您的库的开发层
  2. 您的库的构建层
  3. 消耗库构建的应用程序

第 1 层很简单。在任何您想要创建新工作人员的文件中,比如说它index.ts,您都可以执行您的import X from "worker-loader!./X". 您index.ts确切地知道在哪里可以找到您的工作人员档案。这就是为什么你的 webpack-dev-server 上可以正常工作的原因。

第 2 层是事情变得奇怪的地方。当您使用 处理工作文件时worker-loader,webpack 会输出几个文件。您的配置显示filename: '[name].js',这将为源文件夹中的每个文件输出一个文件,所有文件都位于文件夹中的同一级别_bundles。Webpack 会看到您的import X from "worker-loader!./X",它还会看到您的目标名称和导入文件的位置,以及执行导入的文件。它将输出包Web Worker 文件的位置重写index.js为相对于包其余部分的绝对路径。您可以通过使用publicPath工作加载程序中的选项来更仔细地控制它。但这并不能真正解决问题,因为您只是设置了publicPath绝对路径,这导致我们进入第 3 步...

第 3 层(您尝试使用包的地方)是出现问题的地方。你永远无法预料人们可能会import { makeAWorker } from 'your-library'在他们的代码中尝试做什么。无论他们在哪里导入它,构建文件(在消费者应用程序的 node_modules 中)都将使用 webpack 写入构建的路径来index.js查找工作文件,但现在绝对路径是相对于你的消费者项目(通常是主路径(例如index.html所在的位置),而不是构建的node_modules文件夹。因此您的消费者应用程序不知道在哪里可以找到工作文件。

我的解决方案:一点小技巧

在我的场景中,我认为工作文件的内容足够简单,可以从字符串创建工作人员,并以这种方式导入它。例如,工作文件如下所示:

// worker.ts

// Build a worker from an anonymous function body
export default URL.createObjectURL(
  new Blob([
    '(',
    function () {

      // actual worker content here

    }.toString(),
    ')()', ],
    { type: 'application/javascript' }
  )
);
Run Code Online (Sandbox Code Playgroud)

然后在我想要生成工人的地方:

// index.ts

import workerScript from './worker';

const myWorker = new Worker(workerScript, {worker_options});
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为现在您不再要求 webpack 创建文件并为您写入正确的导入位置。最后,您的工作脚本包中甚至不会有单独的文件。worker-loader你完全可以放弃。您index.ts将创建 Blob 及其 URL,并且您的消费者应用程序将在该 URL 处找到在运行时动态生成的工作脚本。

确实是一个黑客

这种方法有一些严重的缺点,这导致我提出了以下问题:bundle webworkers asintegralpartofnpmpackagewithsinglefilewebpackoutput。问题是在你的工作脚本中,你实际上没有导入任何东西的选项。我很幸运,因为我的工人相对简单。它依赖于单个node_module,该模块本身没有依赖项。我一开始只是在脚本中包含该模块的源代码,但由于我有多个脚本需要相同的外部模块,所以我最终在它生成时将其作为一段数据传递给工作程序:

import { Rainbow } from './utils';

var data = {
  id,
  data              
  RainbowAsString: Rainbow.toString(),
};

myWorker.postMessage(data);
Run Code Online (Sandbox Code Playgroud)

然后在工作人员中,我只需转换RainbowAsString回函数并使用它。如果您想了解更多细节,可以查看我用此方法构建的库:leaflet-topography。查看src/TopoLayer.ts文件以了解工人的使用方式以及src//workers文件夹以了解如何设置工作程序 blob。

结论

我想一定有更好的方法。一种可能的快速修复方法是编写一个复制脚本,将工作文件复制到node_modules/yourLibrary消费者应用程序的构建文件夹中。但这并不能带来很好的可移植性,其他人也必须做同样的事情才能让你的库与他们的应用程序一起工作。我的解决方案并不完美,但它适用于简单的工作脚本。我仍在考虑一个更强大的解决方案,允许工人自己进行进口。

编辑:正确的解决方案:

因此,在写完这个答案后,我受到启发加入对话如何使用它来捆绑库?在 webpack-loader 仓库中。显然,当 webpack 5 大约 2 个月前发布时,他们添加了对此语法的支持:

new Worker(new URL('./path/to/worker.js', import.meta.url))
Run Code Online (Sandbox Code Playgroud)

我还没有尝试过这个,但它看起来像是您需要的解决方案(也是我两个月前想出我的黑客时需要的解决方案)。试试这个 - 它可能正是您需要告诉 webpack 捆绑您的库,同时仍然维护工作脚本和导入它的脚本之间的关系。