在生产中使用JavaScript模块:当前状态。 第二部分

今天,我们发布了材料翻译的第二部分,该部分致力于在生产中使用JS模块。



→顺便说一下,这是本文的第一部分

动态导入


使用真正的导入表达式来分隔代码和加载模块的缺点之一是,使用不支持模块的浏览器来完成的任务是开发人员承担。

而且,如果您想使用动态import()命令来组织延迟代码加载,那么您将不得不处理以下事实:某些浏览器尽管最肯定地支持模块 ,但仍然不支持动态import()命令。 (Edge 16–18,Firefox 60–66,Safari 11,Chrome 61–63)。

幸运的是,这个问题将帮助我们解决一个很小的(大约400个字节的大小)和非常快的polyfill

将此polyfill添加到Web项目非常简单。 您只需要导入它并在应用程序的主入口点对其进行初始化(在调用代码中的任何import()命令之前):

 import dynamicImportPolyfill from 'dynamic-import-polyfill'; //         .   //    ,       . dynamicImportPolyfill.initialize({modulePath: '/modules/'}); 

为了使该方案起作用,需要做的最后一件事就是告诉Rollup,它需要使用您选择的名称(通过output.dynamicImportFunction选项)重命名代码中出现的动态import()命令。 默认情况下,实现动态导入功能的__import__使用__import__名称,但是可以自定义

您需要重命名import()表达式的原因是因为import在JavaScript中是一个关键字。 这意味着不可能通过polyfill来组织用相同名称的命令替换标准import()命令。 如果尝试这样做,将发生语法错误。

但是汇总在项目构建期间重命名命令非常好,因为这意味着您可以在源代码中使用标准命令。 另外,将来,当不再需要polyfill时,将不必重写项目的源代码,从而以不同的方式import以前命名的内容。

高效的JavaScript加载


无论何时使用代码分离,组织已知要很快加载的所有模块的预加载(例如,这些都是主模块的依赖树中的所有模块,这是项目的入口点)都不会受到损害。

但是,当我们加载真实的JavaScript模块时(通过<script type="module"> ,然后通过相应的import命令),我们必须使用modulepreload属性,而不是通常仅用于经典脚本的preload

 <link rel="modulepreload" href="/modules/main.XXXX.mjs"> <link rel="modulepreload" href="/modules/npm.pkg-one.XXXX.mjs"> <link rel="modulepreload" href="/modules/npm.pkg-two.XXXX.mjs"> <link rel="modulepreload" href="/modules/npm.pkg-three.XXXX.mjs"> <!-- ... --> <script type="module" src="/modules/main.XXXX.mjs"></script> 

实际上,在预加载实际模块时, modulepreload绝对比传统的preload机制更好。 事实是,当您使用它时,不仅下载了该文件。 此外,它会立即在主线程之外开始分析和编译脚本。 常规preload无法执行此操作,因为在预加载期间它不知道该文件将用作模块还是常规脚本。

这意味着使用modulepreload属性加载模块通常更快,并且在初始化模块时,不太可能在主线程上产生过多的加载,从而导致接口问题。

创建用于预加载的模块列表


汇总捆绑包对象中的输入片段在其静态依赖关系树中包含完整的导入列表。 因此,在Rollup generateBundle挂钩中,可以轻松获取需要预加载的文件列表。

尽管可以在npm中找到用于生成模块预加载列表的插件,但是为依赖关系树中的每个输入点创建一个类似的列表仅需要几行代码。 因此,我更喜欢使用类似以下代码的方式手动创建此类列表:

 {  generateBundle(options, bundle) {    //         .    const modulepreloadMap = {};    for (const [fileName, chunkInfo] of Object.entries(bundle)) {      if (chunkInfo.isEntry || chunkInfo.isDynamicEntry) {        modulepreloadMap[chunkInfo.name] = [fileName, ...chunkInfo.imports];      }    }    //  -   ...    console.log(modulepreloadMap);  } } 

例如,这里是我如何为philipwalton.com和我的演示应用程序创建一个模块预加载列表 ,我们将在下面讨论。

请注意,尽管modulepreload属性绝对比传统的preload加载模块脚本加载更好,但它对浏览器的支持最差(目前只有Chrome支持)。 如果您的流量中有很大一部分不是来自Chrome,那么根据您的情况,可以使用常规的preload而不是preload

关于preload的使用,我想警告您一些事情。 事实是,当使用preload加载脚本时,与modulepreload不同,这些脚本不会进入浏览器模块映射。 这意味着有可能多次执行预加载请求(例如,如果模块在浏览器完成预加载之前导入了文件)。

为什么要在生产中部署实际模块?


如果您已经使用了webpack之类的捆绑程序 ,并且已经在使用代码拆分和预加载相应文件的方式(类似于我刚才所说的),那么您可能想知道是否应该切换到策略专注于实际模块的使用。 有几个原因使我相信您应该考虑切换到模块。 此外,我相信将项目转换为实际模块要比使用经典脚本和设计用于加载模块的自有代码更好。

ing减少代码总数


如果项目使用实际模块,那么现代浏览器的用户将不必下载一些其他代码,这些代码旨在加载模块或管理依赖项。 例如,当使用实际模块时,您将不需要加载运行时机制和webpack清单

▍改进的预加载代码


如上一节所述,使用modulepreload属性使modulepreload可以在主线程之外加载代码并进行modulepreload和编译。 与preload属性相比,其他所有内容均保持不变。 这意味着,由于使用了modulepreload页面可以更快地进行交互,从而减少了在用户交互过程中阻塞主流的可能性。

结果,无论将应用程序代码划分为多大的片段,使用导入命令和modulepreload属性下载这些片段比使用通常的script标记和通常的preload属性加载这些片段的效率要高得多(特别是如果生成了相应的标记,则尤其如此)动态并在运行时添加到DOM)。

换句话说,由20个模块片段组成的某些项目代码的汇总捆绑包将比由webpack编写的由20个经典脚本片段组成的同一项目的捆绑包更快地加载(不是因为使用了webpack,而是因为这些不是真正的模块)。

ing改善代码的未来重点


浏览器的许多重要新功能都是基于模块构建的,而不是基于经典脚本的。 这意味着,如果您打算使用这些功能,则您的代码应以实际模块的形式显示。 它不应该是在ES5中转译并装载了经典script标签的工具(这是我尝试使用实验性KV Storage API时写的问题)。

以下是一些专门针对模块的最有趣的新浏览器功能:


旧版浏览器支持


在全球范围内, 超过83%的浏览器支持JavaScript模块(包括动态导入),因此,大多数用户将能够使用切换到模块的项目,而无需该项目开发人员的任何特殊努力。

对于支持模块但不支持动态导入的浏览器,建议使用上述dynamic-import-polyfill polyfill 。 由于它很小,并且在可能的情况下使用基于浏览器的标准import()方法,因此使用这种polyfill对项目的大小或性能几乎没有影响。

如果我们谈论的是绝对不支持模块的浏览器,那么要使用它们来组织工作,可以使用module / nomodule模式

example动作例


由于谈论跨浏览器兼容性总是比实现跨浏览器兼容性容易,因此我创建了一个使用上述技术的演示应用程序

此应用程序可在不支持动态import()命令的Edge 18和Firefox ESR等浏览器中使用。 此外,它还可以在不支持模块的Internet Explorer 11之类的浏览器中使用。

为了表明此处讨论的策略不仅适用于简单项目,我在此应用程序中使用了许多大型项目所需的功能:

  • 使用Babel(包括JSX)进行代码转换。
  • CommonJS依赖项(例如react和react-dom)。
  • CSS依赖项。
  • 资源散列
  • 代码分离
  • 动态导入(将后备作为polyfill)。
  • 实现模块/ nomodule模式。

可以在GitHub上找到项目代码(即,您可以分叉存储库并自己构建项目),演示版本托管在Glitch上 ,您可以对其进行试验。

演示项目中最重要的事情是汇总配置 ,因为它决定了如何创建结果模块。

总结


我希望这些材料不仅使您相信在生产环境中部署标准JavaScript模块的可能性,而且还可以确实改善站点加载时间及其性能。

这是在项目中实现模块所需采取的步骤的简要概述:

  • 在ES2015模块支持的输出格式中,使用捆绑器。
  • 积极地进行代码分离(如果可能,直到将依赖关系从node_modules分配到单独的片段中)。
  • 预加载静态依赖关系树中的所有模块(使用modulepreload )。
  • 对不支持动态import()语句的浏览器使用polyfill。
  • 使用模块/无模块模式来组织不支持模块的浏览器的工作。

如果您已经在使用Rollup构建项目,那么我希望您尝试这里所说的内容,并在生产中部署实际模块(使用代码分离和动态导入技术)。 如果您这样做,请让我知道您的情况。 我对了解问题以及成功引入模块的案例感兴趣。

很明显,模块是JavaScript的未来。 我希望并且最好很快地看到我们使用的工具和辅助库如何采用这项技术。 我希望这些材料至少可以对这一过程有所帮助。

亲爱的读者们! 您在生产中使用JS模块吗?

Source: https://habr.com/ru/post/zh-CN466539/


All Articles