【总结】1259- Vite 插件开发实践:微前端的资源处理
最近实现的简单、透明、组件化微前端方案总体感觉不错,也收到了很多人的反馈,很具有学习参考价值。
但有不少朋友使用该方案打包配置出现了一些问题,做事应有始有终,挖的坑总得完善一下。今天分享一下 Vite
针对微应用方案插件开发历程。
通过文章你可以学到:
1. 写一个 Vite 插件
2. 通过 rollup 编译生成一个单独的资源文件
3. import 资源路径处理
4. rollup 一些配置含义
5. 一些解决问题的思路
问题点
总结下来,在 Vite
中使用该微前端方案会遇到如下问题:
Vite
打包后的资源默认是以 HTML 为入口,我们的微前端方案需要以JS
为入口JS 为入口方案打包导出代码被移除掉了
import.meta
语句打包被转译成{}
空对象了chunk
分离后的CSS
文件,Vite
默认以document.head.appendChild
处理打包后的
CSS
文件默认在main.js
中没有引用资源路径手动写
new URL(image, import.meta.url)
太繁琐
通过配置解决问题
首先前三个问题可以通过 Vite
解决。Vite
兼容了 rollup
的配置
问题一,修改 JS
入口则需要修改 Vite
配置,设置 build.rollupOptions.input
为 src/main.tsx
,这样 Vite
会默认以自定义配置的 main.tsx
为入口文件做打包处理,不再生成 index.html
。
问题二,rollup
的一个特性默认会清理掉入口文件的导出模块,可以配置 preserveEntrySignatures: 'allow-extension'
来保证打包之后 export
的模块不被移除掉。
问题三,看了 Vite
的 Issue
,很多人遇到了这个问题,最初以为是 Vite
默认对它做了处理,后面看了 Vite
源码也没有发现处理的逻辑所在,应该是被 esbuild
做了转译。因此将 build.target
设置为 esnext
即可解决问题,即 import.meta
属于 es2020
,设置为具体的 es2020
也行。
配置:
export default defineConfig({build: {// es2020 支持 import.meta 语法target: 'es2020',rollupOptions: {// 用于控制 Rollup 尝试确保入口块与基础入口模块具有相同的导出preserveEntrySignatures: 'allow-extension',// 入口文件input: 'src/main.tsx',},},
});
写 Vite 插件
我们可以写一个插件将上面的配置封装。
一个普通的 Vite
插件很简单
defineConfig({plugins: [{// 可以使用 Vite 和 rollup 提供的钩子},],
});
插件可以做很多事情,通过 Vite
和 rollup
提供的钩子对代码解析、编译、打包、输出的整体流程进行自定义处理。
插件一般不直接写在 vite.config.ts
中,可以定义一个方法导出这个插件,这里可以用 config
这个钩子来提供默认的 Vite
配置,将自定义的配置进行封装:
export function microWebPlugin(): Plugin {// 插件钩子return {name: 'vite-plugin-micro-web',config() {return {build: {target: 'es2020',rollupOptions: {preserveEntrySignatures: 'allow-extension',input: 'src/main.tsx',},},};},};
}
这样一个简单的插件就完成了。
Vite 独有钩子
config - 在解析 Vite 配置前调用,它可以返回一个将被深度合并到现有配置中的部分配置对象,或者直接改变配置
configResolved - 在解析 Vite 配置后调用,使用这个钩子读取和存储最终解析的配置
configureServer - 是用于配置开发服务器的钩子
transformIndexHtml - 转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文
handleHotUpdate - 执行自定义 HMR 更新处理。
rollup 钩子
rollup 钩子非常多,一共分两个阶段
编译阶段:
输出阶段:
这里我们会用到的钩子有:
transform
- 用于转换已加载的模块内容generateBundle
- 已经编译过的代码块生成阶段
样式插入节点处理
问题四,document.head.appendChild
处理
使用
transform
钩子,替换Vite
默认的document.head.appendChild
为自定义节点cssCodeSplit
打包为一个 CSS 文件
我们默认采用 cssCodeSplit
打包为一个 CSS 文件,免去了用插件 transform
修改 Vite 的逻辑。
问题五,即打包后的 CSS
没有引用的问题,获取这个带 hash
的 CSS
我们可以有多种解决方案
使用
HTML
打包模式,抽取index.html
中的JS
、CSS
文件再单独处理不添加样式文件名
hash
,通过约定固定该样式名称通过钩子提取文件名处理
权衡之下,最终采用 generateBundle
阶段提取 Vite
编译生成的 CSS
文件名,通过修改入口代码将其插入。但 generateBundle
已经在输出阶段,不会再走 transform
钩子。
发现一个两全其美的办法:创建极小的入口文件 main.js
,还可以配合 hash
和主应用时间戳缓存处理。
async generateBundle(options, bundle) {// 主入口文件let entry: string | undefined;// 所有的 CSS 模块const cssChunks: string[] = [];// 找出入口文件和 CSS 文件for (const chunkName of Object.keys(bundle)) {if (chunkName.includes('main') && chunkName.endsWith('.js')) {entry = chunkName;}if (chunkName.endsWith('.css')) {// 使用相对路径,避免后续 ESM 无法解析模块cssChunks.push(`./${chunkName}`);}}// 接下面代码
},
生成新的入口文件
通过 bundle
提取可以获取到带 hash
的 JS
、CSS
入口文件了。现在需要写入一个新的文件 main.js
。rollup
中有个 API emitFile
可以触发创建一个资源文件。
接下来对它进行处理:
// 接上面代码
if (entry) {const cssChunksStr = JSON.stringify(cssChunks);// 创建极小的入口文件,配合 hash 和主应用时间戳缓存处理this.emitFile({fileName: 'main.js',type: 'asset',source: `
// 带上 microAppEnv 参数,使用相对路径避免报错
import defineApp from './${entry}?microAppEnv';// 创建 link 标签
function createLink(href) {const link = document.createElement('link');link.rel = 'stylesheet';link.href = href;return link;
}// 入口文件导出一个方法,将打包的 CSS 文件通过 link 的方式插入到对应的节点中
defineApp.styleInject = (parentNode) => {${cssChunksStr}.forEach((css) => {// import.meta.url 让路径保持正确,中括号取值避免被 rollup 转换掉const link = createLink(new URL(css, import.meta['url']));parentNode.prepend(link);});
};export default defineApp;`,});
}
插件需要应用入口配合导出一个 styleInject
方法提供样式插入,我们通过封装入口方法得以解决。
封装一个方法给应用入口调用:
export function defineMicroApp(callback) {const defineApp = (container) => {const appConfig = callback(container);// 处理样式局部插入const mountFn = appConfig.mount;// 获取到插件中的方法const inject = defineApp.styleInject;if (mountFn && inject) {appConfig.mount = (props) => {mountFn(props);// 装载完毕后,插入样式inject(container);};}return appConfig;};return defineApp;
}
现在 build
之后会生成一个不带 hash
的 main.js
文件,主应用可以正常加载打包后的资源了。
进一步优化,main.js
的压缩混淆,可以用 Vite
导出 transformWithEsbuild
进行编译:
const result = await transformWithEsbuild(customCode, 'main.js', {minify: true,
});this.emitFile({fileName: 'main.js',type: 'asset',source: result.code,
});
子应用路径问题
之前我们需要手动添加 new URL(image, import.meta.url)
来修复子应用路径问题。通过 transform
钩子自动处理该逻辑。
在这个插件之前,Vite
会将所有的资源文件转换为路径
import logo from './logo.svg';// 转换为:export default '/src/logo.svg';
因此,我们只需要将 export default "资源路径"
替换为 export default new URL("资源路径", import.meta['url']).href
就可以了。
const imagesRE = new RegExp(`\\.(png|webp|jpg|gif|jpeg|tiff|svg|bmp)($|\\?)`);transform(code, id) {// 修正图片资源使用绝对地址if (imagesRE.test(id)) {return {code: code.replace(/(export\s+default)\s+(".+")/,`$1 new URL($2, import.meta['url']).href`),map: null,};}return undefined;
},
完成,一个比较完善的 Vite
微应用方案由此而生。
看看效果:
更多
有了插件,可以发挥出意想不到的事情。本微前端方案没有实现以下的隔离方式,不保证后续会实现,大家可以发挥更多的想象力。
CSS 样式隔离
通过插件将主应用节点中的 id
添加并修改 CSS
.name {color: red;
}/* 转换为 */#id .name {color: red;
}
但前提是需要为每个 <MicroApp />
设置一个唯一的 id。并且样式性能会受到影响,CSSModules
方案会更好。
JS 沙箱
虽然在 ESM 中做运行时沙箱目前没有现成的方案,但运行时沙箱性能非常差。换个思路,可以从编译时沙箱入手。用 transform
钩子将应用所有的 window
转译为沙箱fakeWindow
,从而达到隔离效果。
代码示例
大家可以 clone
下来学习
插件仓库:https://github.com/MinJieLiu/micro-app/tree/main/packages/micro-vite-plugin
微前端示例:https://github.com/MinJieLiu/micro-app-demo
【总结】1259- Vite 插件开发实践:微前端的资源处理相关推荐
- 当 Vite 遇上微前端
什么是微前端 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略. 微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都 ...
- iframe 接班人-微前端框架 qiankun 在中后台系统实践
「福利」 ✿✿ ヽ(°▽°)ノ ✿:文章最后有抽奖,转转纪念 T 恤,走过路过不要错过哦 背景 在转转的中台业务中,交易流转.业务运营和商户赋能等功能,主要集中在两个系统中(暂且命名为 inner/o ...
- 基于 qiankun 的微前端实践
前言 微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用. 微前端并不是前端领域的 ...
- qiankun微前端实践
qiankun微前端方案实践 qiankun 微前端方案实践 创建qiankun主项目 引入react-app-rewired 配置多环境 配置proxy 创建react子项目 创建vue子项目 个人 ...
- 微前端乾坤js react实践
微前端乾坤js实践 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略.解耦团队之间的龚和渡,解耦技术栈的龚和渡,节省团队间沟通成本,解决技术栈之间无法业务打 ...
- 乾坤 微前端_微前端架构初探以及我的前端技术盘点
前言 最近几年微前端一直是前端界的热门议题, 它类似于微服务架构, 主要面向于浏览器端,能将一个复杂而庞大的单体应用拆分为多个功能模块清晰且独立的子应用,且共同服于务同一个主应用.各个子应用可以独立运 ...
- 字节跳动是如何落地微前端的
大厂技术 高级前端 Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 本文内提及的 Garfish 微前端解决方案已开源:https://github.com/m ...
- qiankun 微前端_看滴普大前端是如何玩转基于“qiankun”(乾坤)的微前端架构的...
前言 「微前端」可以算是 2019年前端技术领域中最热门的话题.各个大厂也纷纷贡献出了自己的解决方案和实践分享.滴普科技作为一家致力于为企业提供数字化转型服务的技术公司,前端也需要灵活聚合,快速复用, ...
- 你怎么会懂?字节跳动是如何落地微前端的?
微前端的出现的背景和意义 微前端是什么:微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小.更简单的能够独立开发.测试.部署的应用,而在用户看来 ...
最新文章
- cocos2d_x_03_经常使用类的使用_事件_画图
- 结构型模式之Bridge模式
- 总结ASP.NET中的各种弹窗
- 从JDK 6升级到JDK 7过程中遇到的一个问题(卸载rpm)
- docker nacos mysql nginx 集群一台
- 统计学习方法-李航(6)
- cocos2dx遇到的坑1
- 宽依赖和窄依赖_Kardemir开始生产窄钢板,进入板材市场
- 好的文案,极大的降低沟通成本
- java电器类代码_阅读下列说明和Java代码,将应填入(n)处的字句写在对应栏内。...
- pe系统如何读取手机_五分钟教会你pe系统制作
- 2022程序员都需要知道的 6 个挺火的开源项目
- emoji 表情图片解决方法
- 用Python写了一个微信聊天机器人
- linux iscsi软件,Redhat Linux 配置 iSCSI 连接存储
- 破解利器C32Asm和IDApro
- 团队项目用户验收评审
- 面试整理:关于代价函数,正则化
- oracle日志在哪里看,Oracle日志文件管理与查看
- 次时代建模的大致流程是什么呢?
热门文章
- Elasticsearch搭建
- 【机器视觉案例】(12) 自制AI视觉小游戏--贪吃蛇,附python完整代码
- [sips]搭建opensip:ubuntu+ARM 64位
- 计算机语言中脚本的意思,文字脚本是什么意思
- 数据库、实体-数据库设计原则-by小雨
- 电脑蓝屏进不了win7系统怎么解决?
- ip地址在同一网段什么意思
- 郑州大学“战役杯”第三次比赛题解
- 自主研发-谭八爷代理下单系统开发
- 导航居中分布css,导航居中背景色通栏显示 DIV + CSS 代码