微前端集成优化:让所有子应用体积更小,加载更快

科技   2024-12-09 07:01   湖南  

简介

随着前端的日益发展,微前端架构越来越受到青睐。它通过将前端应用拆分为多个独立的子应用,每个子应用可以独立开发、部署和运行,从而提升了开发效率和团队协作。目前主流的微前端方案应该是qiankun了。

以笔者公司为例,采用的就是qiankun框架,主应用采用了vue3,子应用五花八门都有。笔者公司前端服务的子应用大约有400多个,后期也会继续增多!因此,如何优化子应用的体积和加载速度,提升用户体验和性能是我们亟待解决的问题!本文将分享我们公司用到的一些优化方案。

优化方案

在代码层的优化,比如按需加载、懒加载、静态资源优化的都是大家熟知的方案了,这里我就不展开了。我们主要的优化在打包上,我们减少体积的两大方案主要是:

  • gzip压缩
  • 依赖共享

本文将详细介绍依赖共享,在了解依赖共享前,我们先了解一下普通的项目打包浏览器初次加载需要请求的文件。

项目初次加载需要请求的文件

对于一个没有特殊配置打包项的普通项目,其核心的请求文件如下:

请求文件
作用
资源尺寸
请求时间
0.js
路由页面内容
(prefetch cache)
60 ms
app.js
main.js等非路由页面内容
893 kB
12 ms
chunk-vendors.js
node_modules内容
37.9 MB
427ms
img

可以看到,影响一个前端服务体积、加载速度的主要文件就是第三方依赖chunk-vendors.js

优化思路

依赖共享

假设我们有非常多的子应用,每个子应用的nodemodlues依赖打包也是单独的,在请求这个子应用时,这个依赖文件也是必须请求的!

但是,我们很多子应用的第三方依赖都是重复的!比如vue的底层依赖、store的依赖、eslint的依赖及一些常用的工具依赖!

如果每个子应用都打包自己的依赖库,这是非常愚蠢的,会导致重复加载,浪费带宽和资源!如果我们能让这些公用的依赖只加载一次,那么,所有子应用都不需要额外请求这些依赖,打包体积也会非常小,这样自然会极大的提升每个子应用加载速度!

那么,我们如何才能实现依赖共享呢?

外部化依赖

最简单的实现方案就是可以将常用的依赖库)配置为外部依赖,不打包在每个子应用中,而是通过CDN加载,如这样:

img

为什么能提高加载速度

当我们采用外部依赖的时候,首先所有应用不需要打包nodemodules的依赖,体积上非常小,在请求基础文件时自然会快很多!

当我们加载主应用时,主应用通过CDN的方式请求了vue的底层依赖、一些常用的公共库等所有依赖。当我们加载子应用时,和主应用相同的这些依赖因为已经请求过了,浏览器会通过缓存机制直接读取已经缓存的数据,避免了重新请求,子应用的加载速度也得到了进一步的提升!

技术方案

目前主流框架如vite、webpack的打包工具默认都是将nodemodlues依赖打包成js文件,我们通过一些配置,就可以将打包方式改变,打包成CDN引文的形式。

使用vite-plugin-cdn-import

首先,安装所需的包

js

代码解读
复制代码npm install vite-plugin-cdn-import --save-dev

然后,创建或编辑你的 vite.config.js 文件,并添加插件配置:

js

代码解读
复制代码import { defineConfig } from 'vite';
import cdnImport from 'vite-plugin-cdn-import';
 
export default defineConfig({
 plugins: [
   cdnImport({
     imports: [
       {
         // 库名,比如 `react`
         libraryName: 'react',
         // 库的CDN地址,比如 `https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js`
         url: 'https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js',
         // 生产环境是否使用CDN
         prod: true,
         // 开发环境是否使用CDN
         dev: false,
       },
       // 可以继续添加其他库的配置...
     ],
   }),
 ],
});

在上面的配置中,libraryName 是你想要替换的库名,url 是CDN上该库的地址。prod 和 dev 分别指示是否在生产环境和开发环境中使用CDN。

现在,当你运行 Vite 开发服务器或构建你的项目时,所有列在 imports 数组中的依赖项都将通过CDN链接注入到你的代码中。

这种方式的缺点就是,所有依赖都要我们一个个配置,非常麻烦!

借助rollup-plugin-html

要在打包时将所有的 node_modules 依赖以<script> 标签的形式引入到 HTML 文件中,我们也可以使用 Rollup 的插件,如rollup-plugin-htmlrollup-plugin-node-resolve,并结合一些自定义逻辑来生成最终的 HTML 文件。以下是一个具体的实现步骤:

安装必要的插件

安装Rollup 插件:

js

代码解读
复制代码npm install --save-dev rollup-plugin-html rollup-plugin-node-resolve rollup-plugin-terser

配置 Rollup

接下来,在 rollup.config.js 中配置 Rollup:

js

代码解读
复制代码import resolve from '@rollup/plugin-node-resolve';
import html from '@rollup/plugin-html';
import { terser } from 'rollup-plugin-terser';
import { defineConfig } from 'rollup';

const generateHTML = () => {
   return {
       name: 'generate-html',
       generateBundle(options, bundle) {
           const scriptTags = Object.keys(bundle)
               .filter(fileName => fileName.endsWith('.js'))
               .map(fileName => `<script lay-src="${fileName}"></script>`)
               .join('\n');

           const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>My App</title>
</head>
<body>
   ${scriptTags}
</body>
</html>
           `
;

           this.emitFile({
               type: 'asset',
               fileName: 'index.html',
               source: htmlContent
           });
       }
   };
};

export default defineConfig({
   input: 'src/main.js',
   output: {
       dir: 'dist',
       format: 'esm',
       entryFileNames: 'static/js/[name]-[hash].js',
       chunkFileNames: 'static/js/[name]-[hash].js',
       assetFileNames: 'static/assets/[name]-[hash][extname]'
   },
   plugins: [
       resolve(),
       terser(),
       generateHTML()
   ]
});

**generateHTML **是一个自定义的 Rollup 插件,用于生成 index.html 文件,并自动插入打包生成的 JavaScript 文件。

  • generateBundle 钩子:在生成包的过程中调用。它通过遍历 bundle 对象中的所有文件,找到以 .js 结尾的文件,并生成相应的
  • this.emitFile 方法:用于将生成的 HTML 内容作为一个新文件输出到 dist 目录中。

上述方案中,我们通过generateBundle钩子实现了所有js文件的遍历与cdn链接的生成。这个方法中,html文件是在vite.config.js中写的,然而我们一般可能更倾向于外部引入index.html。

进一步优化

此方案只给出大致思路:

  • 在vite.config.js中借助generateBundle钩子获取所有文件的cdnList
js

代码解读
复制代码// 借助generateBundle 钩子实现
const cdnList = getCdnList()

这里获取cdnlist的方法请大家自己实现(鉴于公司要求,此部分逻辑无法公开分享)

  • 使用createHtmlPlugin 注入数据

使用 vite-plugin-html 提供的 createHtmlPlugin 插件,将 cdnList 作为数据注入到 HTML 模板中:

js

代码解读
复制代码import { createHtmlPlugin } from 'vite-plugin-html';

const packageName = edgeClient ? 'meos-autocontrol-web-edge' : 'meos-control-web';

export default defineConfig(({ mode }) => {
 const plugins = [
   // ...其他插件
   createHtmlPlugin({
     minify: true,
     inject: {
       data: {
         title: packageName,
         cdnList: cdnList
       }
     }
   }),
   // ...其他插件
 ];

 return {
   // ...其他配置
   plugins,
   // ...其他配置
 };
});
  • 在 index.html 中使用模板语法将 cdnList 渲染到 HTML 中
html

代码解读
复制代码<!DOCTYPE html>
<html lang="en">
   <head>
       <meta charset="UTF-8" />
       <link rel="icon" type="image/svg+xml" href="/vite.svg" />
       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
       <meta http-equiv="Content-Security-Policy" />
       <title><%- title -%></title>
       <% cdnList.forEach(function(cdn) { %> <%- `
       <script src="${cdn}">
</script>
       ` -%> <% }) %>
   </head>
   <body>
       <div id="app"></div>
       <script type="module" src="./src/main.ts"></script>
   </body>
</html>

作者:石小石Orz 链接:https://juejin.cn/post/7373502637730545698


加我微信,拉你进前端进阶、面试交流群,互相监督学习进步等!

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注我的博客 https://github.com/qappleh/Interview,让我们成为长期关系

  3. 关注公众号「深圳湾码农」,持续为你推送精选好文,回复「加群」加入面试互助交流群

点一下,代码无 Bug

深圳湾码农
分享大前端最新技术、BAT大厂面试题、程序员轶事、职场成长等,你想要的这里都有.
 最新文章