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

简介

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

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

优化思路

依赖共享

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

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

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

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

外部化依赖

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

为什么能提高加载速度

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

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

技术方案

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

使用vite-plugin-cdn-import

首先,安装所需的包

npm install vite-plugin-cdn-import --save-dev

然后,创建或编辑你的 vite.config.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-html 和 rollup-plugin-node-resolve,并结合一些自定义逻辑来生成最终的 HTML 文件。以下是一个具体的实现步骤:

安装必要的插件

经安装了必要的 Rollup 插件:

npm install --save-dev rollup-plugin-html rollup-plugin-node-resolve rollup-plugin-terser

配置 Rollup

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

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 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 结尾的文件,并生成相应的 <script> 标签。
    • this.emitFile 方法:用于将生成的 HTML 内容作为一个新文件输出到 dist 目录中。

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

优化方案

此方案只给出大致思路:

  • 借助generateBundle钩子获取所有文件的cdnList

    // 借助generateBundle 钩子实现
    const cdnList = getCdnList()

  • 使用 createHtmlPlugin 注入数据

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

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 中

    <!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>
    <script type="module" src="./src/main.ts"></script> </body> </html>
相关推荐
我要洋人死34 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#