在微前端中加载 Vite 应用
大厂技术 高级前端 Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
自 2018 年 5 月 Firefox 60 发布后,所有主浏览器均默认支持 ES modules。借助 ES modules 的能力,代码可以实现无需构建直接运行。
随着 Vite 和 Snowpack 等基于 ES modules 的构建工具的产生,前端随即掀起了 ES modules 新一轮热潮。
天下武功,无招不破,唯快不破 - 李小龙
Vite、Snowpack 等基于 ES modules 的构建工具带来了开发的极致体验,相比传统的构建工具,这些新型的构建工具或多或少地带来了以下优势:
由于无需打包的特性,服务器冷启动时间超快
借助 ES modules 的能力,模块化交给浏览器处理(虽然目前的阶段存在一个预编译的过程)。传统构建器需要打包依赖和源码,才能构建整个应用,并提供服务。
项目大小不再成为限制项目热更新速度的因素
传统构建器在代码更改时,需要重新构建并载入页面,这样带来的的结果是:随着项目体积增长,构建耗时越长。基于 ES modules 的构建器只进行单文件编译,单文件更新,时间复杂度保持 O(1).
Vite 得益于原生 ES modules 的能力,大幅提升了开发时体验。相信未来,随着社区生态(CDN 服务、Deno)、ESM 相关标准(import-maps、import.meta)的逐步完善,以及越来越多的技术方案解决 ES modules 在浏览器端的相关难题(依赖瀑布,资源碎片化),前端会开启一个无构建的新篇章。
同时在微前端领域,脚本资源的打包规范向来是百花齐放(比如 singleSPA 默认支持 SystemJs 规范,icestark 默认支持 UMD 规范)。未来脚本资源的打包规范必定是趋于统一的 ES modules 规范。正是基于这两个原因,微前端支持 ES modules 应用的加载就成了用户强诉求。
加载 ES modules 微应用
Vite 会默认打包出符合标准的 ES modules 的脚本资源。ES modules 资源的加载方式如下:
<script src="index.js" type="module">script>
然而,在 icestark 中需要依赖微应用导出 生命周期函数 来渲染微应用。使用 js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"`
shouldRemove = true
}
}
...
Vite 默认使用 index.html
作为入口,在解析 index.html
的过程中,会生成一个虚拟的入口文件,将脚本资源通过 import
注入进来,也就是最终的入口文件实际上类似于下面的代码:
import './src/main.ts';
import 'polyfill';
面对这个场景,我们想到了两种解决方案:
借助 Vite Lib 模式,修改应用入口:
// vite.config.ts
export default defineConfig({
...
build: {
lib: {
entry: './src/main.ts',
formats: ['es'],
fileName: 'index'
},
rollupOptions: {
preserveEntrySignatures: 'exports-only'
}
},
})
这种方式有个明显的问题是:Vite 以 Lib 模式构建出的应用,其产物并不是一个完整的前端应用(缺少 index.html),无法满足独立运行的条件。
通过插件修改 Vite 的这一默认行为
通过 vite-plugin-index-html 插件,结合 Vite 的解析能力,将入口修改为静态资源的入口。
import htmlPlugin from 'vite-plugin-index-html';
// vite.config.ts
export default defineConfig({
plugins: [vue(), htmlPlugin({
input: './src/main.ts'
})]
})
ice.js Vite 模式
同时,icestark 也支持 ice.js Vite 模式快速接入。安装或升级 build-plugin-icestark
插件,在微应用 build.json
中配置:
{
+ "vite": true,
"plugins": [
[
: ,
}]
]
}
即可得到正确导出生命周期函数的微应用。详细用法可参见 使用 ice.js Vite 模式。
最终效果
渐进升级
为了解决时间上,长尾应用升级带来的效率问题,微前端通常是大型架构升级所选择的中间态(或终态)方案。因此在设计加载 ES modules 方案时,需要保持这一基准原则。
框架应用可以保持现有的构建方式不变(仍然可以使用 webpack 等非原生 ES modules 构建工具),亦无需对框架应用做任何构建上的改造。
因此,基本可以无痛尝试 Vite 所带来的快感,脚踏实地地,一点点地靠近远方。
二次加载的极致体验
通过对 ES modules 原理的探寻,可以知道 ES modules 只执行一次。换成实际例子,也就是说当第二次执行相同的加载脚本时:
// icestark 第二次执行加载脚本
const { mount, unmout } = import(esModule);
浏览器不会重复执行 Construction -> Instantiation -> Evaluation 的流程,而是直接返回上次模块执行的结果。这会导致一些副作用的操作(比如在 Module Conext 下插入样式资源,脚本资源的行为,这给我们的微应用二次加载带来了额外的问题),同时也带来了极快的二次加载效果。
建立在原生 ES modules 规范下的应用不会在短时间内快速铺开,很多 To C,To 商户的业务对浏览器的版本仍有限制。但是,icestark 在 2.x 快一年多的发展以来,仍希望覆盖到多样的开发场景,提供便捷、快速地业务升级。在支持传统 JS bundle、UMD 规范,本文分享了 icestark 在接入 ES modules 规范微应用的一些尝试,希望能给开发者带来一些新的选择和启发。
引用
icestark - 面向大型系统的微前端解决方案
proposal-dynamic-import
Vite - Next Generation Frontend Tooling
ES modules: A cartoon deep-dive
What Happens When a Module Is Imported Twice?
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
“分享、点赞、在看” 支持一波👍