各位网友晚上好,相信大家都对使用type="module"和nomodule这种技巧有所耳闻。今天我将为大家分享我把这种技巧应用到极致的经验:4 条件加载方案。
什么是分条件加载
在前端构建中,我们通常需要进行 Babel 语法降级和 polyfill 插入等操作以兼容低版本浏览器。但这会导致一个问题:在现代浏览器中也会加载大量兼容性代码,无法享受浏览器原生支持的新特性带来的性能优势。
分条件加载的核心思想是:为不同能力的浏览器提供差异化的构建产物。最基础的实现就是利用 HTML5 标准中的type="module"和nomodule属性进行区分:
html
<!-- 仅供参考 -->
<script type="module" src="modern-bundle.js"></script>
<script nomodule src="legacy-bundle.js"></script>
这样在支持 ES Module 的浏览器中会加载type="module"的脚本,而不支持的浏览器会忽略这个标签并加载带有nomodule属性的脚本。通过这种方式,现代浏览器就能直接运行原生 class、ES Module、解构赋值等新特性,无需加载冗余的兼容性代码。
3 个条件的分条件加载
由于IE8与IE9之间有巨大的差异,因此在上述2个版本的构建产物之上,使用条件注释在分出第3个版本专用于IE8及以下。
html
<!-- 仅供参考 -->
<!--[if gte IE 9]><!-->
<script type="module" src="modern-bundle.js"></script>
<script nomodule src="legacy-bundle.js"></script>
<!--><![endif]-->
<!--[if lte IE 8]>
<script src="ie8-bundle.js"></script>
<![endif]-->
这样IE8及以下加载特制兼容包可以更加精简,其他版本也能避开恶心的兼容代码。
为什么需要分4条件加载
分3个版本似乎己经够用了,但是随着top-level-await
的出现,变得不够用了。使用type="module"和nomodule分版本的一个目的就是能使用原生esmodule 。而top-level-top
出现后,无法用旧有的esmodule实现top-level-await
。因此只能使用传统方式,比如构建成AMD格式 ,用AMD加载器加载。因此我们可以进一步细分出一个版本。 现在我们有4个版本了
- IE8及以下
- 不支持原生esm的版本(chrome4~chrome60)
- 支持原生esm但不支持top-level-await(chrome61~chrome89)
- 原生支持top-level-await的浏览器(chrome90+)
注意哦,上面最新的版本是chrome90,现在都chrome130+了,随着浏览器的更新,我希望用新浏览器的用户能够直接运行新特性,因此我需要用一个较新的特性做区分,于是我选择了Promise.try。现在的4个版本改变为
- IE8及以下
- 不支持原生esm的版本(chrome4~chrome60)
- 支持原生esm但不支持Promise.try(chrome61~chrome127)
- 原生支持Promise.try的浏览器(chrome128+)
这里我选择了Promise.try 是因为我还没有在公司推开es2025的使用,业务人员是不允许使用es2025的特性,代码中也必然不存在es2025的代码。等我在公司构建工具链中做完了es2025的降级处理,就会把用Promise.try判断,改成用更新的特性来判断。
html
<!-- 仅供参考 -->
<!--[if gte IE 9]><!-->
<script type="module">
if(Promise.try){__import__("xxxx")}else{__import__("yyyy")}
</script>
<script nomodule src="legacy-bundle.js"></script>
<!--><![endif]-->
<!--[if lte IE 8]>
<script src="ie8-bundle.js"></script>
<![endif]-->
最后我们看看打包后的运行效果。我们发现在最新浏览器中没有polyfill引用,也可以原生运行top-level-await。
总结
通过多条件加载,可以在全浏览器兼容的前提下,减少新式浏览器的加载内容大小。分4版本条件加载,是做到极致的水平。