likes
comments
collection
share

体验webpack5个人总结

作者站长头像
站长
· 阅读数 35

介绍webpck5

距离webpack5推出也快有一年多的时间,现在还处在测试版本的阶段,目前已经可以安装使用了

安装方式

```
npm install webpack@next webpack-cli -D
yarn add webpack@next webpack-cli -D
```

HTML插件兼容HTML

  • 需要将html-webpack-plugin升级到4.3.0版本

环境要求

  • 最新的官方文档要求node至少在10.13.0版本以上

新特性介绍

  • 持久化缓存
  • moduleIds & chunkIds的优化
  • 更智能的tree shaking
  • nodeJs的polyfill脚本被移除
  • 支持生成e6/es2015的代码
  • SplitChunk和模块大小
  • Module Federation

使用体验

  • 经过上手使用,webpack5打包体积大小,持续编译速度都有很不错的提升,对webpack4兼容也很平缓,Module Federation也对项目中如何使用微型前端应用提供一种解决方案。如果升级过程中有遇不兼容的情况,可以去webpack5变更日志上查阅。

持久化缓存

  • 在webpack5之前,可以使用cache-loader将编译结构写入硬盘缓存,还可以使用babel-loader,设置option.cacheDirectorybabel-loader编译的结果写进磁盘。
  • 在webpack5中,默认开启缓存,缓存默认是在内存里。可以对cache进行设置
module.export={
    cache{
       type:'filesystem',  //  'memory' | 'filesystem'
        cacheDirectory: 'node_modules/.cache/webpack', // 默认将缓存存储在 node_modules/.cache/webpack
        // 缓存依赖,当缓存依赖修改时,缓存失效
        buildDependencies:{
        	// 将你的配置添加依赖,更改配置时,使得缓存失效
        	config: [__filename]
    	} 
    }
}
  • 默认情况下webpack 会假定其所处的 node_modules 目录仅由包管理器修改,将会跳过hash和时间戳处理,出于性能考虑,仅使用package的名称和版本。

  • 当设置 cache.type: "filesystem" 时,webpack 会在内部以分层方式启用文件系统缓存和内存缓存。从缓存读取时,会先查看内存缓存,如果内存缓存未找到,则降级到文件系统缓存。写入缓存将同时写入内存缓存和文件系统缓存。

  • 文件系统缓存不会直接将对磁盘写入的请求进行序列化。它将等到编译过程完成且编译器处于空闲状态才会执行。如此处理的原因是序列化和磁盘写入会占用资源,并且我们不想额外延迟编译过程。

  • 缓存淘汰策略设计:文件缓存存储在 node_modules/.cache/webpack,对于一个缓存集合,最大限度应该不超过 5 个缓存内容,最大累积资源占用不超过 500 MB,当逼近或超过 500MB 的阈值时,优先删除最老的缓存内容。同时,也设计了缓存的有效时长为 2 个星期。

moduleIds & chunkIds的优化

chunk 和 module

  • chunk:webpack打包最终生成的单独文件块,最终生成的单独文件,一个文件对应一个chunk。
  • module:每一个源码 js 文件其实都可以看成一个 module。

chunkId的缺点

  • webpack5改进了moduleIdschunkIds的确定。在webpack5之前,没有从entry打包的chunk文件,都会以1,2,3...的文件命名方式输出。(文件名称后的hash值是用chunkhash生成的)

体验webpack5个人总结

  • 这样会造成一个后果是,当删除或者暂时不使用1.js这个文件后,那么2.js->1.js,3.js->2.js,这样就会造成原本线上的2.js请求时会造成缓存失效.
  • 在webpack5之前也是可以通过webpackChunkName来解决命名问题
...
<Switch>
      <Route key='/' exact path='/' component={
         Loadable({
             loader: () => import(/* webpackChunkName: "home" */ './home'),
                     loading: (<div>loadding</div>)
                 })
         }/>
         <Route key='/page1' exact path='/page1' component={
             Loadable({
                loader: () => import(/* webpackChunkName: "page1" */'./page1'),
                    loading: () => (<div>loadding</div>)
                })
         } />
        <Route key='/page2' exact path='/page2' component={
           Loadable({
              loader: () => import(/* webpackChunkName: "page2" */'./page2'),
                  loading: () => (<div>loadding</div>)
               })
         } />
</Switch>
....
  • 这样似乎解决了缓存失效的问题,但我们打开编译后的home.js 会发现还是存有chunkId。如果删除掉home这个菜单,page1,page2打包后的chunkId还是会发生变化,

体验webpack5个人总结

  • page1.js打包的文件 体验webpack5个人总结
  • 删除完home.js后page1.js打包的文件

体验webpack5个人总结

  • 即使page1.js没有做任何修改,但是由于home.js删除导致的chunkId的变化,所以page1.js的chunkhashi还是会发生变化,缓存失效的问题依旧存在。

webapck5 的改进

  • 采用新的算法,在生产模式下,默认启用这些功能chunkIds: "deterministic", moduleIds: "deterministic"
  • 此算法采用确定性的方式将短数字 ID(3 或 4 个字符)分配给 modules 和 chunks。这是基于 bundle 大小和长效缓存间的折中方案。
 optimization.moduleIds: 
 可选值:
 1. false  告诉webpack不应使用任何内置算法,通过插件提供自定义算法
 2.natural 按使用顺序的数字ID。
 3.named 方便调试的高可读性id
 4.deterministic 根据模块名称生成简短的hash
 5.size 根据模块大小生成的数字id
 optimization.chunkIds: 
 可选值:
 1. false  告诉webpack不应使用任何内置算法,通过插件提供自定义算法
 2.natural 按使用顺序的数字ID。
 3.named 方便调试的高可读性id
 4.deterministic 根据模块名称生成简短的hash
 5.size 根据请求到的初始资源size计算的id
 6..total-size:根据请求到的解析资源size计算的id

更智能的tree shaking

  • 开启tree shaking的条件跟webpack4一样,必须使用ES6模块化,开启production环境
  • 例子
// a.js
export const a =22;
export const b = 33;

// b.js
import * as a from './a.js'
export {a};

// index.js
import * as b from './b.js'

console.log(b.a.a)  // 输出22
  • webpack4:打包时,会把const b =33也打包进去

体验webpack5个人总结

  • webpack5:打包的出来的代码则更精简

体验webpack5个人总结

nodeJs的polyfill脚本被移除

  • webpack <= 4 附带了许多 Node.js 核心模块的 polyfil,一旦模块中使用了任何核心模块(即 ”crypto“ 模块),这些模块就会被自动启用。
  • 虽然这使得为 Node.js 编写模块变得简单,但它会将超大的 polyfill 添加到 package 中。在许多情况下,这些 polyfill 并非必要。
import CryptoJS from 'crypto-js';
console.log(CryptoJS.MD5('123123'));
  • webpack4 : 体验webpack5个人总结
  • webpack5: 体验webpack5个人总结

支持生成e6/es2015的代码

  • webpack5 在output中新添加了ecmaVersion,设置output: { ecmaVersion: 6 }就可以使用,在webpack中默认编译生成的代码对应的是es5版本。
  • 两种设置方式:5 =< ecmaVersion <= 11 2009 =< ecmaVersion <= 2020

SplitChunk和模块大小

  • 模块现在能够以更好的方式表示大小,而不是显示单个数字和不同类型的大小。
  • 默认情况下,只能处理 javascript 的大小,但是你现在可以传递多个值来管理它们:
optimization{
  splitChunks{
  minSize: {
        javascript: 30000,
        style: 50000,
    }
  }
}

Module Federation

  • Module Federation 使 JavaScript 应用得以从另一个 JavaScript 应用中动态地加载代码 —— 同时共享依赖。让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了!
  • 动态加载 可以允许代码在运行时按需加载另一个应用的代码。
  • 例子:在app1中引用app2中暴露出来的一个按钮组件
// app1 - webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
 //....
 plugins: [
  new ModuleFederationPlugin({
   name: "app1",  // 应用名称 唯一
   library: { type: "var", name: "app1" },
   remotes: { // 需要引用远程应用,与app2中library的name字段保持一致
    app2: "app2",
   },
   shared: ["react", "react-dom"], // 先判断存不再存这个包,如果不存在就使用app2里的依赖
  }),
 ],
};
// app2 - webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
 //....
 plugins: [
  new ModuleFederationPlugin({
   name: "app2", // 应用名称 唯一
   library: { type: "var", name: "app2" }, // UMD标准导出,和name保持一致即可。
   filename: "remoteEntry.js", 暴露出去的chunkname
   exposes: {  要暴露出去的模块
    "./Button": "./src/Button",
   },
   shared: ["react", "react-dom"], 与app1共享的依赖,如果app1中有,则会优先使用app1中的依赖。(注:被app1引用)时候会按照app1的/package.json中的版本要求来加载
  }),
 ],
};
// app1/index.html
// 引入app2暴露的文件
...
<head>
	<srcipt src="http://localhost:3002/remoteEntry.js"></script>
</head>
...
app1/app.js

import AppTwoButton from 'app/Button';
const App = ()=>{
	return (
		<AppTwoButton/>
	)
}

app1/bootstrap.js
ReactDOM.render(<App />, document.getElementById("root"));

app1/bootstrap.js
import("./bootstrap");

  • 从加载流程看懂Module Federation

体验webpack5个人总结

  • 首先加载的是 app2/remoteEntry.js ,因为是在html中加载的,通过观remoteEntry.js 可以看出,webpakc是将app2定义在了全局变量里面了,这样app1就能够加载app2里面的模块了。

体验webpack5个人总结

  • 加载app1的打包后的main.js,进入到bootstrap.js寻找依赖前置,分析
  • 然后在加载main.js里的react.js以及react-dom,react依赖的prop-types.js
  • 接着经过remote的时候,发现依赖app2的Button组件
  • bootstrap.js所有依赖都加载完之后,异步加载完成,执行then逻辑,启动应用

总结

  • Module Federation给微前端,代码细分,共享依赖,按需加载提供新的思路,而且能做到运行时加载。但缺点公用全局变量和全局style,以及将模块暴露在全局变量下不够优雅。
  • webpack5还在不断合并代码的过程中,目前版本bate22,webpack5正式版出来后,相信Module Federation功能还会继续优化。总之升级webpack5之后,构建速度和打包体积都有不错的提升。

参考资料

转载自:https://juejin.cn/post/6850037264962027534
评论
请登录