【问题标题】:Efficient dynamic import of multiple scripts in module type Web Worker模块类型 Web Worker 中多个脚本的高效动态导入
【发布时间】:2025-10-14 11:35:02
【问题描述】:

我有一个需要在启动时动态加载多个脚本的网络应用程序。为了提高效率,它需要通过网络并行请求所有脚本。为了正确起见,脚本必须按特定顺序执行。

在 DOM 中,这可以通过动态添加 <script> 标签并将 async 属性设置为 false 来实现。请求并行发出,脚本按照添加标签的顺序运行。

在经典类型的Web Worker(默认)中,可以使用importScripts(...arr)。浏览器一次性获取所有脚本的列表,因此可以同时请求它们,但要保证脚本按指定的顺序运行。

在模块类型的 Web Worker 中,事情变得更加棘手。 importScripts 不允许在模块类型工作者中使用(如果您尝试,Chrome 会抛出 Module 脚本不支持 importScripts())。工人不能添加脚本标签。但是,您可以使用动态 import() - 但它只接受一个脚本。如果您像这样一次导入一个脚本:

async LoadScripts(scriptsArr)
{
    for (const src of scriptsArr)
        await import(src);
}

这样会序列化网络请求一个接一个地运行,效率低。

我唯一能想到的就是动态创建一个仅包含 import '...'; 行的模块,然后动态导入它,如下所示:

async LoadScripts(scriptsArr)
{
    // Create a string with a module full of import statements
    const moduleStr = scriptsArr.map(src => `import '${src}';`).join("\n");

    // Create a blob from the string
    const blob = new Blob([moduleStr], { type: "application/javascript" });

    // Dynamic import the blob, which in turn loads all the import statements
    await import(URL.createObjectURL(blob));
}

这感觉就像一个非常丑陋的黑客,就像一个美化的eval()。有没有更好的办法?

【问题讨论】:

  • "我有一个 web 应用程序需要在启动时动态加载多个脚本" - 你能详细说明一下要求吗?为什么它是动态的,它如何决定哪些脚本需要加载,哪些不需要?如果它们以意外的顺序加载会出现什么问题?
  • 我们在浏览器中制作了 Construct 3,一个完整的游戏开发 IDE。游戏预览在本地工作并且是模块化的,可能包括本地安装的第三方插件。所以在预览中加载的脚本没有固定的列表,依赖运行时的第三方脚本必须在运行时脚本之后,并且运行时也有自己的内部依赖项(正如我在另一条评论中提到的,我们正在从经典脚本迁移,所以还没有导入依赖链)
  • 我认为对于没有服务器交互的预览功能,createObjectURL 是一个非常好的和合适的解决方案。然后,游戏的实际构建可能会将其生成为静态文件。

标签: javascript performance web-worker es6-modules dynamic-import


【解决方案1】:

为了正确起见,脚本必须按特定顺序执行。

如果脚本确实有依赖关系,它们应该使用 import 语句显式声明它们。

创建一个以正确顺序导入所有内容的单个模块,就像您动态构建的模块一样,也是一种选择,可能模块列表实际上是相当静态的。

如果一次导入一个脚本,会序列化网络请求一个接一个运行,效率低。

如果模块声明了它们的依赖关系,您可以一次加载它们:

function loadScripts(scriptsArr) {
    return Promise.all(scriptsArr.map(src => import(src));
}

【讨论】:

  • 我们正在从经典脚本迁移,因此我们使用“仅用于副作用的导入”语法,即import 'foo.js';
  • @AshleysBrain 但是,如果您不使用模块功能,为什么?以及如何处理导出,将所有内容放在全局范围内?