文章目录
- 一、构建速度优化
- 二、包代码体积的优化
- 1.代码分割splitchunk
- 2.Tree Shaking
- 3.剔除npm包里面针对Node.js模块自动引用的Polyfills
- 三、持久化缓存的优化
- 四、模板联邦
在使用webpack的时候,我们常常会做一些优化,比如:
- 构建速度优化
- 代码体积优化
- 持久化缓存优化
- Module Federation
webpack5做了哪些优化呢?
一、构建速度优化
在webpack4中,为了让我们的构建速度更快,我们通常需要借助一些插件或一些额外的配置来达到目的。
- cache-loader,针对一些耗时的工作进行缓存。比如缓存babel-loader的工作。
- terser-webpack-plugin 或 uglifyjs-webpack-plugin的cache以及parallel。(默认开启)
比如我们会借助 cache-loader 去对我们构建过程中消耗性能比较大的部分进行缓存,缓存会存放到硬盘中node_modules/.cache/cache-loader,缓存的读取和存储是会消耗性能的,所以只推荐用在性能开销大的地方。
// 对babel-loader的工作进行缓存
module.exports = {module: {rules: [{test: /\.js$/,use: ['cache-loader', 'babel-loader'],include: path.resolve('src'),},],},
};
erserPlugin继承自uglifyjsPlugin,我们可以开启插件的cache以及parallel特性来加快压缩。(terserPlugin是webpack推荐及内置的压缩插件,cache与parallel默认为开启状态)缓存路径在node_modules/.cache/terser-webpack-plugin
optimization: {minimizer: [new TerserPlugin({cache: true, // 开启该插件的缓存,默认缓存到node_modules/.cache中parallel: true, // 开启“多线程”,提高压缩效率exclude: /node_modules/})],
},
到了webpack5,可以通过cache 特性来将webpack工作缓存到硬盘中。存放的路径为node_modules/.cache/webpack
// webpack.config.js
module.exports = { cache: {// 1. 将缓存类型设置为文件系统type: 'filesystem', // 默认是memory// 2. 将缓存文件夹命名为 .temp_cache,// 默认路径是 node_modules/.cache/webpackcacheDirectory: path.resolve(__dirname, '.temp_cache')}
}
二、包代码体积的优化
1.代码分割splitchunk
为了让我们的打出来的包体积更加小,颗粒度更加明确。我们经常会用到webpack的代码分割splitchunk以及tree shaking。在webpack5中,这两者也得到了优化与加强。比如
splitChunks: {chunks: 'all',minSize: {Javascript: 30000,style: 50000,}
},
// 默认配置
module.exports = {//...// https://github.com/webpack/changelog-v5#changes-to-the-configuration// https://webpack.js.org/plugins/split-chunks-plugin/optimization: {splitChunks: {chunks: 'async', // 只对异步加载的模块进行处理minSize: {Javascript: 30000, // 模块要大于30kb才会进行提取style: 50000, // 模块要大于50kb才会进行提取},minRemainingSize: 0, // 代码分割后,文件size必须大于该值 (v5 新增)maxSize: 0,minChunks: 1, // 被提取的模块必须被引用1次maxAsyncRequests: 6, // 异步加载代码时同时进行的最大请求数不得超过6个maxInitialRequests: 4, // 入口文件加载时最大同时请求数不得超过4个automaticNameDelimiter: '~', // 模块文件名称前缀cacheGroups: {// 分组,可继承或覆盖外层配置// 将来自node_modules的模块提取到一个公共文件中 (又v4的vendors改名而来)defaultVendors: { test: /[\\/]node_modules[\\/]/,priority: -10},// 其他不是node_modules中的模块,如果有被引用不少于2次,那么也提取出来default: {minChunks: 2,priority: -20,reuseExistingChunk: true}}}}
};
2.Tree Shaking
tree shaking 的意思是,webpack 在打包的时候将会剔除掉被没有被使用到的代码达到减小报体积,缩短 http 请求时间,起到一定效果的页面优化。
Webpack 不能百分百安全地进行 tree-shaking,webpack4 有些场景是不能将无用代码剔除的。有些模块导入,只要被引入,就会对应用程序产生重要的影响。一个很好的例子就是全局样式表,或者设置全局配置的Javascript 文件。
Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用程序。Webpack 的设计者清楚地认识到不知道哪些文件有副作用的情况下打包代码的风险,因此webpack4默认地将所有代码视为有副作用。这可以保护你免于删除必要的文件,但这意味着 Webpack 的默认行为实际上是不进行 tree-shaking。值得注意的是webpack5默认会进行 tree-shaking。
webpack4 曾经不进行对 CommonJs 导出和 require() 调用时的导出使用分析。webpack 5 增加了对一些 CommonJs 构造的支持,允许消除未使用的 CommonJs 导出,并从 require() 调用中跟踪引用的导出名称。
3.剔除npm包里面针对Node.js模块自动引用的Polyfills
v4编译引入npm包,有些npm包里面包含针对nodejs的polyfills,实际前端浏览器是不需要的
例如:
// index.js
import CryptoJS from 'crypto-js';
const md5Password = CryptoJS.MD5('123123');
console.log(md5Password);
v4 引入crypto-js模块会自动引入polyfill: crypto-browserify, 但部分代码是不需要的,v5 默认会自动剔除
v5编译中,会出现polyfill添加提示,如果不需要node polyfille,按照提示 alias 设置为 false 即可
// webpack.config.jsresolve: {// 1.不需要node polyfilssalias: {crypto: false},// 2.手动添加polyfills// fallback: {// "crypto": require.resolve('crypto-browserify')// }}
到了webpack5,我们需要清楚自己的项目需要引入哪些node polyfill。更加了配置的门槛,但是减少了代码的体积。
webpack5中将path、crypto、http、stream、zlib、vm的node polyfill取消后
三、持久化缓存的优化
以前v4是根据代码的结构生成chunkhash,现在v5根据完全内容生成chunkhash,比如改了内容的注释或者变量则不会引起chunkhash的变化,让浏览器继续使用缓存。
在webpack4 中,chunkId与moduleId都是自增id。也就是只要我们新增一个模块,那么代码中module的数量就会发生变化,从而导致moduleId发生变化,于是文件内容就发生了变化。chunkId也是如此,新增一个入口的时候,chunk数量的变化造成了chunkId的变化,导致了文件内容变化。
webpack4可以通过设置optimization.moduleIds = 'hashed’与optimization.namedChunks=true来解决这写问题,但都有性能损耗等副作用。
optimization: {moduleIds: 'hashed',namedChunks: true,// ...
}
而webpack5 在production模式下optimization.chunkIds和optimization.moduleIds默认会设为’deterministic’,webpack会采用新的算法来计算确定性的chunkI和moduleId。
四、模板联邦
模块联邦制,使 Javascript 应用得以从另一个 Javascript 应用中动态地加载代码 —— 同时共享依赖。项目分为Host(消费者),remote(被消费者)。功能实现主要依靠 ModuleFederationPlugin 插件。
new ModuleFederationPlugin({name: '', // 名称,唯一idlibrary: {}, // 以什么形式暴露,比如umd filename: '', // 输出的入口文件名称exposes: {}, // 要输出的组件或方法shared: [] // 要共享的依赖
})
例子:
module.exports = {// other webpack configs...plugins: [new ModuleFederationPlugin({// 1. name 当前应用名称,需要全局唯一name: "app_one_remote",// 2. remotes 可以将其他项目的 name 映射到当前项目中remotes: {app_two: "app_two_remote",app_three: "app_three_remote"},// 3. exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用exposes: {AppContainer: "./src/App"},// 4. shared可以让远程加载的模块对应依赖改为使用本地项目的 React或ReactDOM。shared: ["react", "react-dom", "react-router-dom"]}),new HtmlWebpackPlugin({template: "./public/index.html",chunks: ["main"]})]
};
比如设置了remotes: { app_two: “app_two_remote” },在代码中就可以直接利用以下方式直接从对方应用调用模块
import { Search } from "app_two/Search";
app_two/Search来自于app_two 的配置:
// app_two的webpack 配置
export default {plugins: [new ModuleFederationPlugin({name: "app_two",library: { type: "var", name: "app_two" },filename: "remoteEntry.js",exposes: {Search: "./src/Search"},shared: ["react", "react-dom"]})]
};
正是因为 Search在exposes被导出,我们因此可以使用 [name]/[exposes_name] 这个模块,这个模块对于被引用应用来说是一个本地模块。
这个方案是直接将一个应用的 bundle,应用于另一个应用,动态分发 runtime 子模块给其他应用。
参考文章https://www.jianshu.com/p/eacdd98d25b0
参考文章https://github.com/HolyZheng/holyZheng-blog/issues/48
本文链接https://blog.csdn.net/qq_39903567/article/details/115335822