开发者问题收集

如何将自己的库与需要跨越 WebWorkers 和 AudioWorklets 的 WebPack 集成

2022-01-16
1142

目标 我是 JavaScript 库的作者,该库可通过 AMD 或 ESM 在各种运行时环境(浏览器、Node.js、开发服务器)中使用。我的库需要使用其包含的文件生成 WebWorkers 和 AudioWorklet。该库会检测其运行的上下文,并为执行上下文设置所需的内容。

只要用户(用户 = 我的库的集成者)不将 WebPack 等捆绑器带入游戏,这种方法就很好用。要生成 WebWorker 和 AudioWorklet,我需要包含我的库的文件的 URL,并且我需要确保调用我的库的全局初始化例程。

我更愿意在我的库中完成尽可能多的繁重工作,而不是要求用户仅为使用我的库而进行非常专业的自定义设置。把这项工作交给他们通常会立即产生适得其反的效果,人们会提出问题,寻求帮助将我的库集成到他们的项目中。

问题 1: 我建议我的用户确保将我的库放入自己的块中。用户可以根据自己的设置设置块,只要其他库不会给工作人员带来任何麻烦或副作用即可。尤其是像 React、Angular 和 Vue.js 这样的现代 Web 框架是这里典型的问题,但也有人们试图将我的库与 jQuery 和 Bootstrap 捆绑在一起。所有这些库在包含在 Workers/Worklets 中时都会导致运行时错误。

分块通常使用一些 WebPack 配置完成,例如:

config.optimization.splitChunks.cacheGroups.alphatab = {
  chunks: 'all',
  name: 'chunk-mylib',
  priority: config.optimization.splitChunks.cacheGroups.defaultVendors.priority + 10,
  test: /.*node_modules.*mylib.*/
};

mylib 现在有一个大问题:生成的 chunk-mylib.js 的绝对 URL 是什么,因为现在这是我的库的准入口点,并且已经进行了捆绑和代码拆分:

  • document.currentScript 通常指向某个入口点,例如 app.js ,而不是块。
  • __webpack_public_path__ 指向用户在 webpack 配置中设置的任何内容。
  • __webpack_get_script_filename__ 可以使用 如果 知道块名称,但我还没有找到获取我的库所包含的块名称的方法。
  • import.meta.url 指向我的库的原始 .mjs 的某个绝对 file:// url。
  • new URL(import.meta.url, import.meta.url) 导致 WebPack 生成带有一些哈希值的附加 .mjs 文件。此附加文件不是所需的,并且生成的 .mjs 包含一些附加代码,破坏了它在浏览器中的使用。

我已经在考虑创建一个自定义 WebPack 插件,它可以解析我的库所包含的块,以便我可以在运行时使用它。我更愿意使用尽可能多的内置功能。<​​/p>

问题 2: 假设问题 1 已解决,我现在可以使用正确的文件生成一个新的 WebWorker 和 AudioWorklet。但由于我的库被包装到 WebPack 模块中,因此我的初始化代码将不会被执行。我的库仅存在于“块”中,而不是 entry ,我不知道这种拆分会允许 mylib 在浏览器加载块后运行一些代码。

在这里我相当无知。也许分块并不是实现此目的的正确拆分方式。也许需要一些其他设置,我尚未意识到这是可能的?

也许使用自定义 WebPack 插件也可以最好地完成此操作。

问题的可视化表示 :使用建议的分块规则,我们得到如块中所示的输出。问题 1 是红色部分(如何获取此 URL),问题 2 是橙色部分(如何确保在后台工作程序/工作单元启动时调用我的启动逻辑)

visual example

实际项目 我想分享我的实际项目,以便更好地理解我的用例。我说的是我的项目 alphaTab ,一个音乐符号渲染和播放库。在浏览器 UI 线程 (app.js) 上,人们将组件集成到 UI 中,并获得一个与组件交互的 API 对象。一个 WebWorker 负责音乐表的布局和渲染,第二个 WebWorker 合成音频样本以供播放,AudioWorklet 将缓冲的样本发送到音频上下文以供播放。

3个回答

我认为应该将 worker 代码作为资产而不是源代码来处理。也许您可以添加一个简单的 CLI 来在项目根目录下生成一个“.alphaTab”文件夹,并添加指示,让用户将其复制到“dist”或“public 文件夹”。 即使想出了一个 Webpack 特定的解决方案,您也必须绕过其他捆绑器/设置(Vite、rollup、CRA 等)。

编辑:您还需要在初始化中添加一个可选参数来传递脚本路径。不是完全自动化的,但比设置复杂的捆绑器配置更简单

Tiago Nobrega
2022-06-01

禁用 import.meta

关于 import.meta.url 此链接 可能会有所帮助。您似乎可以通过将 module.parser.javascript.importMeta 设置为 false 来禁用 webpack 配置。

重新设计整体架构

对于其余部分,这听起来有点混乱。您可能不应该尝试将完全相同的块代码导入到您的 worker/worklet 中,因为这高度依赖于 webpack 如何生成和使用块。即使你今天设法让它工作,如果 webpack 团队改变了他们内部表示块的方式,它将来可能会崩溃。

同样,从用户的角度来看,他们只想导入库并让它工作,而不用摆弄所有不同的构建步骤。

相反,一种更干净的方法是为主库、AudioWorklet 和 Web Worker 生成单独的文件。而且由于您已经设计了 worklet 和 web worker 来使用您的库,您可以只为它们使用预先构建的非模块库,并为 webpack/其他捆绑器的入口点提供单独的文件。

最直接的方式是让用户将您原始的非模块 js 库添加到他们构建的捆绑包中,并让 es 模块使用该非模块库的 url 加载 Web Workers 和 Audio Worklet。

当然,从用户的角度来看,如果他们不必复制其他文件并将它们放在正确的目录中(或配置脚本目录),那会更容易。直接的方法是从 CDN 加载 Web Worker 或 Worklet(例如 https://unpkg.com/@coderline/ [email protected] /dist/alphaTab.js ),但是跨源加载 Web Worker 存在限制,因此您必须使用一种解决方法,例如先获取它,然后从 Blob URL 加载它(例如 此处 )。不幸的是,这会导致初始化 Worker/Worklet 异步。

捆绑 Worker 代码

如果这不是一个选项,您可以通过将 Worker/Worklet 代码字符串化并通过 blob 或数据 url 加载它,将库、Web Worker/Worklet 代码捆绑到一个文件中。在您的特定用例中,考虑到捆绑输出中将重复多少代码,从效率的角度来看,这有点痛苦。

对于这种方法,您将有多个构建步骤:

  1. 构建 Web Worker 和/或 Audio Worklet 使用的库。
  2. 通过字符串化以前的库/库来构建单个库。

这一切都很复杂,因为库、Web Worker 和 Audio Worklet 只有一个入口文件。从长远来看,您可能会通过重写这些不同目标的入口点而受益,但目前,我们可以重用当前的工作流程并使用不同的插件更改构建步骤。对于第一次构建,我们将制作一个插件,当它尝试导入工作库时,它会返回一个虚拟字符串;对于第二次构建,我们将让它返回该库的字符串化内容。我将使用 rollup,因为这是您的项目使用的。下面的代码主要用于说明目的(将工作库保存为 dist/worker-library.js );我还没有真正测试过它。

第一个插件:

var firstBuildPlugin = {
  load(id) {
    if (id.includes('worker-library.js')) {
      return 'export default "";';
    }
    return null;
  }
}

第二个插件:

var secondBuildPlugin = {
  transform(code, id) {
    if (id.includes('worker-library.js')) {
      return {
        code: 'export default ' + JSON.stringify(code) + ';',
        map: { mappings: '' }
      };
    }
    return null;
  }
}

使用这些插件,我们可以通过 import rawCode from './path/to/worker-library.js'; 导入 web worker/audio worklet 库。对于您的情况,由于您将重复使用同一个库,您可能需要创建一个带有导出的新文件,以防止多次捆绑相同的代码:

libraryObjectURL.js

import rawCode from '../dist/worker-library.js'; // may need to tweak  the path here
export default URL.createObjectURL(
  new Blob([rawCode], { type: 'application/javascript' })
);

并实际使用它:

import libraryObjectURL from './libraryObjectURL.js'; // may need to tweak the path here
//...
var worker = new Worker(libraryObjectURL);

然后实际构建它,您的 rollup.config.js 看起来像:

module.exports = [
    {
        input: `dist/lib/alphatab.js`,
        output: {
            file: `dist/worker-library.js`,
            format: 'iife', // or maybe umd
            //...
            plugins: [
                firstBuildPlugin,
                //...
            ]
        }
    },
    {
        input: `dist/lib/alphatab.js`,
        output: {
            file: `dist/complete-library.mjs`,
            format: 'es',
            //...
            plugins: [
                secondBuildPlugin,
               //...
            ]
        }
    },
    // ...     

保留旧代码

最后,对于您的其他构建,您可能仍希望保留旧路径。您可以使用 @rollup/plugin-replace 来实现此目的,方法是使用将在构建过程中替换的占位符。

在您的文件中,您可以将:

var worker = new Worker(libraryObjectURL);

替换为:

var worker = new Worker(__workerLibraryURL__);

并在构建过程中使用:

  // ...
  // for the first build:
  plugins: [
    firstBuildPlugin,
    replace({ __workerLibraryURL__: 'libraryObjectURL')
    // ...
  ],
  // ...
  // for the second build:
  plugins: [
    secondBuildPlugin,
    replace({ __workerLibraryURL__: 'libraryObjectURL')
    // ...
  ],
  // ...
  // for all other builds:
  plugins: [
    firstBuildPlugin,
    replace({ __workerLibraryURL__: 'new URL(import.meta.url)') // or whatever the old code was
    // ...
  ],

如果 AudioWorklet 网址不同,您可能需要使用另一个替代网址。如果未使用 worker-library 文件,则导入的 libraryObjectURL 将被树状图删除。

未来工作:

您可能需要研究为不同的目标提供多个输出:Web Worker、音频 Worklet 和库代码。它们实际上不应该加载完全相同的文件。这将消除对第一个插件(忽略某些文件)的需求,并且可能使事情更易于管理和高效。

更多阅读:

Steve
2022-06-03

我找到了一种解决所述问题的方法,但仍然存在一些未解决的痛点,因为 WebPack 开发人员更愿意尝试避免使用特定于供应商的表达式,而更愿意依赖“可识别的语法结构”来根据他们的需要重写代码。

该解决方案无法在完全本地的环境中工作,但它可以与 NPM 一起使用:

我现在使用 /* webpackChunkName: "alphatab.worker" */ new Worker(new URL('@coderline/alphatab', import.meta.url))) 启动我的工作器,其中 @coderline/alphatab 是通过 NPM 安装的库的名称。此语法结构 由 WebPack 检测 ,并将触发生成一个新的特殊 JS 文件,其中包含一些 WebPack 引导程序/入口点,用于加载启动时的库。因此,它在编译后有效地处理如下:

编译后

要使其正常工作,用户应配置 WebPack 以将库放在自己的块中。否则,可能会发生库内联到 webpack 生成的工作文件中,而不是也从公共块加载的情况。在没有公共块的情况下,它也可以工作,但它甚至会违背使用 webpack 的好处,因为它会复制库的代码以将其生成为 worker(加载时间和磁盘使用量增加一倍)。

不幸的是,目前这仅适用于 Web Workers,因为 WebPack 目前不支持 Audio Worklet

此外,由于 WebPack 产生的循环依赖关系,还会出现一些警告,因为 chunk-alphatab.js 和 alphatab.worker.js 之间似乎存在一个循环。在此设置中,这应该不是问题。

就我而言,UI 线程代码和在 worker 中运行的代码没有区别。如果用户决定通过设置渲染到 HTML5 画布,则渲染发生在 UI 线程中,如果使用 SVG 渲染,则会将其卸载到 worker。两侧的整个布局和渲染管道均相同。

Danielku15
2022-06-12