[《抛开脚手架,徒手搭建 react 项目(一)》](juejin.cn/post/739816… ")我们用webpack搭建了一个react项目,引入了typescript,用Babel编译,还用webpack-merge拆分了我们的配置文件。
《抛开脚手架,徒手搭建 react 项目(二)》我们在项目里面引入了eslint,还介绍了打开服务的五种方法。尤其重要的是 nginx,要你在本地体验一把线上奔跑的感觉。
《抛开脚手架,徒手搭建 react 项目(三)》我们在项目里面引入了 stylelint、还有husky相关工具,轻松实现git commit的时候,对代码格式和提交信息做具体的检测。
《抛开脚手架,徒手搭建 react 项目,webpack打包优化篇(四)》亲手测试并整理了六点减少打包时间的手段。
抛开脚手架,徒手搭建 react 项目,webpack打包优化篇(五) 亲手测试并整理了六点能够缩减打包体积的手段
今天来接上篇,继续优化webpack,上篇已经道明,优化webpack的手段分为两个途径:缩短打包时间和减小打包体积。上篇我们用:缩减打包范围,插件loader的缓存,定向搜索,分离第三方包,增加多线程,添加查看打包速度和打包体积的方法对webpack做了打包时间的优化。
总结为:定向文件,提前打包,专业分析,多手工作。
现在继续篇未完成部分:减少打包体积
一.分割代码
分割各个模块代码,提取相同部分代码,好处是减少重复代码的出现频率
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后便能按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大减小加载时间。
常用的代码分离方法有三种:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 入口依赖 或者 SplitChunksPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用分离代码。
1.多入口打包
尽管 Webpack 允许在配置中使用多个入口起点,但在可能的情况下,最好使用具有多import的单入口打包,这样才能获得更好的优化效果,并确保异步脚本标签的执行顺序一致。
Webpack文档不推荐多入口的原因有如下:
- 每个入口起点都会生成一个输出文件,这意味着如果你使用多个入口起点,就会生成多个输出文件。这种情况下,每个输出文件都需要加载并执行,会增加额外的网络请求和加载时间。
- 相比之下,使用多导入单入口作为起点,可以将所有的代码逻辑打包到一个输出文件中,从而减少了网络请求和加载时间。这样可以提高应用程序的性能和加载速度。
- 如果多入口,单出口也会引入重复引用的问题。
1.配置
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
runtimeChunk: 'single',
},
};
2.测试
index.js:包含 ./src/index.js 的代码。
another.js:包含 ./src/another-module.js
的代码。 shared.js:包含 lodash 的代码。
runtime.js:包含 Webpack 运行时代码。
不推荐就不用呗,别没事给自己找事。反正现在我们的页面是SPA页面,是单入口多导入的,非常契合webpack的宗旨。
2.代码分隔
webpack的代码分隔是通过它的内置插件SplitChunksPlugin实现的,我们不用引入这个插件,直接在优化项里面配置splitchunks就好了!
1.配置
optimization: {
runtimeChunk: { name: "manifest" }, // 抽离WebpackRuntime函数
splitChunks: {
cacheGroups: {
common: {
minChunks: 2, // 代码块出现最少次数
name: "common", // 代码块名称
priority: 5, // 优先级别
reuseExistingChunk: true, // 重用已存在代码块
test: AbsPath("src")
},
vendor: {
chunks: "initial", // 代码分割类型
name: "vendor",
priority: 10,
test: /node_modules/
}
}, // 缓存组
chunks: "all" // 代码分割类型:all全部模块,async异步模块,initial入口模块
}
},
代码分隔的时候,一定要在output里面配置动态输出,不然就会报错
2.测试
其实不管代码怎么分隔,怎么打包,都是为了浏览器能够快速的加载渲染,根本就不影响页面上显示出来的结果。
3.react项目热更新
配置webpack.config.js
const { HotModuleReplacementPlugin } = require("webpack");
.....
plugins: [
!isDev && new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html', // 指定一个HTML模板文件
filename: './index.html', // 输出的HTML文件名,默认是index.html
inject: true, // 允许插件修改哪些内容,true将脚本添加到body元素的末尾
cache: true,
}),
new StylelintPlugin({
files: ["src/**/*.{scss,css,less}"], // 指定要检查的文件
fix: true, // 是否自动修复一些样式错误
cache: true
}),
new EslintPlugin({ cache: true }),
new ProgressBarPlugin({ complete: '█', }),
new HotModuleReplacementPlugin(),
],
react项目还要在引入module.hot才能用,所以说webpack的热更新并不是一个开箱即用的模块,而是你的手动引入进来才行。不过现在好了,有个插件可以帮我做这些事情
安装命令
npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D
配置webpack。config。js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = merge(baseConfig, {
// ...
plugins: [
new ReactRefreshWebpackPlugin(), // 添加热更新插件
]
})
为babel-loader配置react-refesh刷新插件,修改babel.config.js文件
热更新的时候,webpack,webpack-cli,webpack-dev-server的版本至关重要,千万不要乱下版本,不然你就芭比Q了,你有解决不完的bug
具体可以参照这篇文章:juejin.cn/post/735356…
二.tree-shaking处理js
Tree Shaking的意思就是摇树,伴随着摇树这个动作,树上的枯叶都会被摇晃下来
tree-shaking意思就是在webpack打包过程中,它会生成一颗依赖树,在这颗依赖树上会明确的标注清楚,哪些文件export了,但是没有import,最后在生成target文件的时候,就把哪些没有import的文件干掉。
在webpack5里面,只要我们的mode:production,它就会自动trr-shaking
1. 开发环境下的配置
// webpack.config.js
module.exports = {
// ...
mode: 'development',
optimization: {
usedExports: true,
}
};
2.生产环境下的配置
// webpack.config.js
module.exports = {
// ...
mode: 'production',
};
测试
三.动态垫片
在webapck打包的过程中,我们往往需要对代码做兼容处理。比如我们写的代码使用了async await,有些浏览器就不支持这个关键字,怎么办?就需要@babel/polyfill出马,把他编译成浏览器能看懂的代码。但是问题是浏览器不一样,支持的属性也不一样,所以@babel/polyfill有个配置项是:useBuiltIns,
- false:无视
target.browsers将所有Polyfill加载进来 - entry:根据
target.browsers将部分Polyfill加载进来(仅引入有浏览器不支持的Polyfill,需在入口文件import "core-js/stable") - usage:根据
target.browsers与检测代码中ES6的使用情况将部分Polyfill加载进来(无需在入口文件import "core-js/stable")
使用html-webpack-tags-plugin在打包时能够,自动加入动态垫片,可根据浏览器UserAgent返回当前浏览器Polyfill
具体配置如下:
import HtmlTagsPlugin from "html-webpack-tags-plugin";
export default {
// ...
plugins: [
// ...
new HtmlTagsPlugin({
append: false, // 在生成资源后加入
publicPath: false, // 使用公共路径
tags: ["https://polyfill.alicdn.com/polyfill.min.js"] // 资源路径
})
]
};
四.按需加载
按需加载:是页面加载成功以后,并不着急加载这些文件,而是等用到了再去加载文件。最大的好处是减轻首屏渲染的负担。因为项目功能越多其打包体积越大,导致首屏渲染速度越来越慢。
动态引入的都要重启下生效!
动态导入react的组件用React.lazy(()=>import('./User'));
动态导入函数直接用import('./utils/math')
1.配置
如果直接用import()加载组件是渲染不出来的,比如下图
2.测试
包打出来,启动看看
并没有渲染出我的组件,为什么?因为import是异步的
修改成React.lazy(()=>import('./user'))
打包执行还是报错了
翻了很久才找到解决方案,原来是babel编译问题,加上下面这个,打包就好了
如果你的动态导入还在报错就尝试一下:@babel/plugin-syntax-dynamic-import
五.作用提升
作用提升最早出现的rollup上,是rollup的核心概念。它的的作用是分析模块之间的关系,然后把打包好的模块全部合并到一个函数里面。
在webpack打包过程中,将大部分函数都打包成一个一个的IIFE,就是自执行函数。这样会使代码的体积不断增大,导致占用的内存也越来越多。
有了作用提升后,构建好的代码,会根据引用的顺序存放到一个函数作用域里面,通过各种手段防止变量名冲突,减少函数声明,和内存花销。
在webpack中只需将打包环境设置为生产环境就能让作用提升生效,或显示设置concatenateModules。
export default {
// ...
mode: "production"
// ...
optimization: {
// ...
concatenateModules: true
}
};
六.压缩资源
前提条件是:浏览器本就支持 gzip压缩包,所以你打包后的文件压缩以后,传递给浏览器,浏览器会自动解压,然后执行。
压缩HTML/CSS/JS代码,字体/图像/音频/视频 的 好处是更有效减少打包体积,
1.压缩html文件
压缩方式:
- 移除 HTML 文件中的注释。
- 压缩 HTML 文件,例如删除空格和换行符。
有些博文说需要配置: html-webpack-plugin,不过我发现只要mode:production它就会自动压缩,mode:development就不会压缩测试看看
2.压缩css文件
如果没有具体的插件处理css文件,那么打包以后,css文件是包含在js文件里面,要想压缩,那么第一步就是先把css文件分离出来,使用mini-css-extract-plugin插件。
安装
npm install --save-dev mini-css-extract-plugin
配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ...
module: {
rules: [
{
test: /.less$/,
use: [
isDev ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader",
'postcss-loader',
"less-loader"
]
}
// ... 其他规则
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css'
}),
// ... 其他插件
]
// ...
};
测试看看
为了压缩输出文件,请使用类似于 css-minimizer-webpack-plugin 这样的插件。 安装
npm i css-minimizer-webpack-plugin -D
配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
],
module: {
rules: [
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
// `...`,
new CssMinimizerPlugin(),
],
},
};
测试
3.压缩js代码
-
uglifyjs-webpack-plugin:压缩
ES5版本的JS代码,一般用于webpack4里面。 -
terser-webpack-plugin:压缩
ES6版本的JS代码,一般用于webpack5里面。
具体用哪个取决于你webpack的版本,但是我发现只要mode:production,webpack就会对代码做压缩,即使我不配置
terser-webpack-plugin
测试如下:
我们安装下terser-webpack-plugin,看他在mode:development下面的打包结果是什么
npm install terser-webpack-plugin --save-dev
配置
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
测试结果:
就说明只要是mode:production它就会对js代码做压缩,我们就不要再安装压缩工具了。
总结:
webpack有12个优化方向,要么是缩减打包时间,要么是缩减打包体积。缩减打包时间的目的是要我们能够快速的打包,提高开发效率。缩减打包体积是节省浏览器加载资源的时间,提升页面的性能。