乐趣区

关于前端工程化:翻译篇-ES-模块预加载和完整性

翻译篇 – ES 模块预加载和完整性

<!– TOC –>

  • 翻译篇 – ES 模块预加载和完整性

    • 生产环境中模块的优化
    • 模块预加载
    • 模块预加载例子:
    • Polyfilling 模块预加载
    • 完整性限度
    • 号召一起口头
    • JSPM Generator – 模块预加载生成器
  • 参考
  • 社交信息 / Social Links:

<!– /TOC –>

生产环境中模块的优化

当在生产环境中应用 ES 模块时,目前有两种次要的性能优化要利用 - 代码拆分和预加载。

代码拆分优化可用于打包器(如 esbuild 或 RollupJS)中的原生 ES 模块。代码拆分确保对于任何两个总是被一起加载的模块,它们将始终被内联到同一个模块文件中,作为网络优化的块模块(或者甚至在可能的状况下,会被内联到入口模块自身中)。

而后预加载解决了模块依赖图中模块按援用程序加载的问题 – 模块仅在动态导入图中的每个模块都加载后才执行,模块仅在其父模块加载后才加载。参考:实操探索之 ESM 引入顶级 await 前后模块的执行程序

模块预加载

ES 模块的预加载在浏览器中如下写法:

<link rel="modulepreload" href="..."/> 

当它首次在 Chrome 中公布时,Google Developers 2017 更新中有一篇对于它的精彩文章。

倡议尽可能为所有深度依赖项注入 modulepreload 标签,以便齐全打消模块加载的提早老本,这同时也是是动态预加载的次要益处。

【应用 modulepreload 的另一个次要益处是,它是目前应用“integrity”属性反对所有加载模块齐全完整性的惟一机制。】(不太明确, 等我查找材料)

比方 app.js 加载 dependency.js 加载 library.js,咱们能够这样写:

<link rel="modulepreload" href="/src/app.js" integrity="sha384-Oe38ELlp8iio2hRyQiz2P4Drqc+ztA7jb7lONj7H3Cq+W88bloPxoZzuk6bHBHZv"/>

<link rel="modulepreload" href="/src/dependency.js" integrity="sha384-kjKb2aJJUT956WSU7Z0EF9DZyHy9gdvPOvIWbcEGATXKYxJfkEVOcuP1q20GT2LO"/>

<link rel="modulepreload" href="/src/library.js" integrity="sha384-Fwh0dF5ROSVrdd/vJOvq0pT4R6RLZOOvf6k+ijkAyUtwCP7B0E3qHy8wbot/ivfO"/>

<script type="module" src="/src/app.js"></script>

因为预加载导致 app.js、dependency.js 和 library.js 当初立刻并行加载,因而模块提早加载被打消,【并且通过所有脚本的完整性,咱们能够齐全爱护模块执行环境。】(不太明确, 等我查找材料)

模块预加载例子:

代码:
index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <!-- <link rel="modulepreload" href="./index.js"/>
    <link rel="modulepreload" href="./init.js"/>
    <link rel="modulepreload" href="./a.js"/>
    <link rel="modulepreload" href="./b.js"/> -->
</head>
<body>
    <script src="./index.js" type="module"></script>
</body>
</html>

index.js:

import './init.js'
import {a, Person, obj, b} from './a.js'

console.log(a, Person, obj, b)


console.log('index')

init.js:

import './b.js'

console.log('init')

export let str = 'init'

const a = 1

export {a}

a.js:

import * as bb from "./b.js"

console.log('b:', bb)

export let a = 1

export function Person() {}

export const obj = {name: "sss"}

let b = 1

export {b}

b.js:

export let a = 1

export function Person() {}

export const obj = {name: "sss"}

let b = 1

export {b}

console.log('b')

未应用模块预加载:
模块是串行加载的,按模块的解析程序进行加载

应用模块预加载:(去掉 index.html 代码正文)
所有模块是并行加载的(加载工夫被显著缩短 200ms)

然而,如果我的项目的模块很多的话,那么这样一个一个手动加,也不是好方法,所以最好是能够先获取到整个模块依赖图,而后通过脚本将模块预加载的标签主动退出到页面上。

然而,话又说回来,如果能够获取到整个模块依赖图的话,那么整个模块依赖就曾经开始在加载了。参考深刻 ES Module, 浅析原理

Polyfilling 模块预加载

此性能的一个问题是它目前仅在 Chromium 浏览器中实现,但能够应用以下代码构建 polyfill:

<script>
  function processPreload () {const fetchOpts = {};
    if (script.integrity)
      fetchOpts.integrity = script.integrity;
    if (script.referrerpolicy)
      fetchOpts.referrerPolicy = script.referrerpolicy;
    if (script.crossorigin === 'use-credentials')
      fetchOpts.credentials = 'include';
    else if (script.crossorigin === 'anonymous')
      fetchOpts.credentials = 'omit';
    else
      fetchOpts.credentials = 'same-origin';
    fetch(link.href, fetchOpts);
  }
  for (const link of document.querySelectorAll('link[rel=modulepreload]'))
    processPreload(link);
</script>

此外,如果咱们想为这个 polyfill 增加动静预加载反对,那么咱们能够应用渐变观察者:

new MutationObserver(mutations => {for (const mutation of mutations) {if (mutation.type !== 'childList') continue;
    for (const node of mutation.addedNodes) {if (node.tagName === 'LINK' && node.rel === 'modulepreload')
        processPreload(node);
    }
  }
}).observe(document, { childList: true, subtree: true});

【这不会填充深度依赖预加载,但涵盖了大多数用例,并通过启动外部完整性映射在所有模块浏览器中启用“完整性”查看。】(不太明确, 等我查找材料)

通过这种形式,咱们能够在所有浏览器的生产模块环境中取得残缺的预加载和完整性反对。

下面的 polyfill 蕴含在 ES Module Shims 的最新 0.12.1 版本中,它为各种 ES 模块性能提供了 polyfill 的组合,特地是对于导入地图。

完整性限度

应用 modulepreload 作为次要完整性办法存在一个次要问题是:没有一个简略的办法,能够在不立刻执行预加载的状况下,为提早加载的模块事后提供完整性。对于当初的生产工作形式,最好的办法是构建一个自定义的动静导入包装器,在触发动静导入之前提早注入预加载。

只管采纳这样方法,这项工作的工作量也不小,而且可能会带来比拟大的处突,如果有人采纳这样的办法,我会感到诧异。然而,模块完整性是应用 ES 模块 CDN 的相对要害个性。

在将来,可能与模块预加载 和 完整性相干的标准包含:

  • Import assertions:已倡议应用 import ‘mod’ assert {integrity: ‘…’} 语法,但尚未为 import assertions 进行规定或实现。

    可怜的是,此性能存在以下问题:它勾销了导入映射的次要性能劣势,即容许图形中的所有模块有更长生效工夫能够独立缓存。因而,尽管它对某些特定状况很有用,但作为这个问题的个别解决方案,它会倒退一步。

  • Import Map Integrity:我曾经为导入地图倡议了一个完整性属性标准,容许它们在这里充当编排点。这里的艰难是从浏览器中取得反对,但到目前为止还没有胜利。
  • Milestones:这是一个实验性的性能办法,应用以后的 Proposal Doc 和 Chrome CL,旨在容许指定一些对于脚本加载条件的信息,以便进行更细粒度的加载优化。可怜的是,目前没有打算为预加载反对此性能,因而这意味着它无奈以以后模式解决提早模块加载的深度图内容完整性问题。
  • Lazy Preloads:另一种设计可能是在预加载标签上有一个属性来批示它不应被预加载,但其完整性值仍应实用。我在 WebAppSec 上提出了这个倡议,但这里的预加载和完整性的组合仿佛有些凌乱。
  • Web Bundles:从常见问题解答中,仿佛 Web Bundle 的以后完整性状态因为哈希验证的高带宽老本而被视为后续提案。

兴许通用模块完整性的概念能够折叠成一个集中的 Web 完整性清单,可能与平安清单中的其余权限 / 平安性能相结合。相同,验证能够从咱们用于优化完整性的宽泛办法中受害,包含针对分块边界优化的默克尔树,甚至更奇异的货色。

号召一起口头

上网的人应该可能拜访互联网上的网站,并且所有执行的代码都通过完整性哈希验证,这一根本准则是一项相对根本的平安属性。

以最佳形式实现这项工作须要一些新的工作,咱们须要踊跃与浏览器供应商和规范机构单干,以确保能够为将来的 Web 平台齐全轻松地启用此平安属性,而不会呈现摩擦。

JSPM Generator – 模块预加载生成器

JSPM Generator 是我始终致力于针对模块 CDN 或本地包门路生成导入映射的低级工具。最新版本当初包含对模块依赖项的动态跟踪的反对,容许为模块图构建这些预加载标签。改良这些生成器 API 和工作流程的工作正在进行中。

通过切换应用程序右上角的“预加载”和“完整性”框,JSPM.io 的在线生成器也蕴含对这些性能的反对。

这是实际操作的演示,为独自的 CDN 依赖项切换预加载:

<img src=”https://guybedford.com/jspm-generator-preload.gif” width=”90%”>

参考

  • ES Module Preloading & Integrity
退出移动版