manifest.json在微前端中的妙用

3,527 阅读5分钟

本文中的manifest.json

关于manifest.json的常见使用,相信大家去Google或者百度一下,能在很多技术和场景中看到这个文件,它的使用也是有比较大的差异。这里主要讲下的是在Webpack打包中,这个文件的介绍和使用,以及分享下笔者在实践中利用Webpack打包产生的manifest.json,实现微前端场景下资源加载优化的目的。

Webpack中的manifest

为了方便起见,我们先拿umi,简单创建个演示项目,有兴趣的同学也可以用纯净的webpack去配置下相同的效果【笔者也有类似的习惯,可以说是洁癖,但是确实更能体会到本质】

mkdir myapp && cd myapp
npx @umijs/create-umi-app

然后执行npm i安装下依赖

安装完成后,我们修改下配置,因为umi默认配置是不产生manifest的,修改.umirc.ts,详细或者更多配置可以参考umi的manifest配置

然后我们执行npm run build,执行打包完成后我们会得到如图所示的dist目录和manifest.json的内容

怎么样来简单理解下这个manifest.json的作用呢?更详细的定义,大家可以参考webpack的文档链接

相信大家或多或少听到过Windows的注册表的概念,可以类比下,这个文件就是记录了我们这个项目打包的注册信息。因为umi的打包的工具中默认配置了入口的js文件是umi.jscss文件入口是umi.css,更多的时候比如像笔者的团队的打包工具设置的入口文件是index.jsindex.css这个大同小异,所以我们在资源引用的时候 会变成baseUrl+ /+index.js,当然在微前端场景中也是类似,实际比较大型的项目会把整个打包产物的目录部署到nginx的静态目录下,或者上传CDN服务上去。

从实际效果来说,如果是单个spa项目,因为HtmlWebpackPlugin的存在和使用,可以在index.html中注入相应的入口信息;如果是微前端项目,我们会在对应的界面的某个菜单配置加载某个前端项目的资源入口地址,比如https://xxxx/tms/index.js,nginx映射关系背后的实际目录可能是 /mnt/cdn/tms/dist/index.js

关于微前端的概念和实践,如果大家有兴趣,笔者可以后期再介绍下实战经验

**

由此就带来一个实际的问题,这段地址是一个静态地址,不论我代码发布了多少次,因为url地址并没有发生变化,用户的浏览器可能会缓存这些文件,导致使用存在问题。有Webpack实际使用经验的同学,这时候肯定会想到,这个非常好解决呀,我在资源引用处修改下变成https://xxxx/ddv/index.js?213123123123Date.now()拼接个字符串上去,或者给打包的文件加上hash,nice!非常聪明。

但是这样虽然解决了被缓存的问题,但是引发了另外两个问题。

  1. 添加时间戳,导致了缓存彻底失效,用户每次刷新界面都完整了重新加载了资源,有点浪费
  2. 文件名加上了hash值,资源引用处就需要同步打包后的文件名。这就带来了比较大的管理成本【其实很多大厂或者团队在实践微前端应用过程中,为了精准控制,是存在对应的管理模块和对应更新策略的,有些实践中是这里的hash,也有在nginx资源处添加了一层目录就是版本号的概念】

因为有个manifest的存在,笔者实践了一套能够兼顾的办法,大家可以讨论下。

在微前端项目中,改造下资源加载入口,考虑大家理解成本和项目代码的私密性,放出段简化代码

const load=(jsIndex,cssIndex)=>{
  // todo 根据自身技术形态去考虑
};

// 此处,可以通过时间戳或者post请求的方式,避免manifest.json文件本身的缓存
const manifestUrl = url + '/manifest.json' + `?${Number(new Date())}`;
axios(manifestUrl).then((json) => {
  const cssIndex = json['index.css'];
  const jsIndex = json['index.js'];
  let count = 0;
  for (let i = 0; i < jsIndex.length; i++) {
    if (jsIndex[i] === '.') {
      count++;
    }
  }// 演示方便 low点,可以采用正则或者其他方式去匹配下
  if (count >= 2) {
    // hash模式 index.123123.js
    load(jsIndex,+ cssIndex);
  } else {
    // 兼容老的模式 即index.js,实际项目中需要考虑向前兼容,微前端项目经常是几十个,未必能够一次性改造完成,尤其是接入第三方的代码
    load(`${url}/index.js?${Number(new Date())}`, `${url}/index.css?${Number(new Date())}`);
  }

}).catch(e => {
  console.error(e);
});

这里实际上,也会有同学肯定会差异,manifest.json的请求还是需要每次都请求。的确是的,首先是这个文件本身大小可观,再者如果是比较完备的微前端管理策略下,此处实现也需要去请求相应的接口去获得相关信息,所以是必要的。因为manifest.json是每次打包产生的,因此在没有项目升级和更新的情况下,入口hash值并不变,比如https://xxxx/adc/index.12asdn.js,能够被用户浏览器缓存,避免每次都去重新请求,在项目代码发生改变后,manifest.json的信息发生改变,浏览器又能去请求到最新的资源,一举两得。

当然,笔者再次声明,此方案仅限于这么一个基于现实场景简化得来的场景讨论,实际生产项目往往会采用更加复杂和全面的方案去实现,但是作为理解manifest.json和微前端资源加载本身还是有不错的作用,如果是有些中小型的项目,如果有类似的需求,也不妨去完善下再去使用。

参考资料:

  1. Webpack - The Manifest
  2. umi - hash配置

更多内容可以关注公众号 - 喵爸的小作坊