[《抛开脚手架,徒手搭建 react 项目(一)》](juejin.cn/post/739816… ")我们用webpack搭建了一个react项目,引入了typescript,用Babel编译,还用webpack-merge拆分了我们的配置文件。
《抛开脚手架,徒手搭建 react 项目(二)》我们在项目里面引入了eslint,还介绍了打开服务的五种方法。尤其重要的是 nginx,要你在本地体验一把线上奔跑的感觉。
《抛开脚手架,徒手搭建 react 项目(三)》我们在项目里面引入了 stylelint、还有husky相关工具,轻松实现git commit的时候,对代码格式和提交信息做具体的检测。
《抛开脚手架,徒手搭建 react 项目,webpack打包优化篇(四)》亲手测试并整理了六点减少打包时间的手段。
抛开脚手架,徒手搭建 react 项目,webpack打包优化篇(五) 亲手测试并整理了六点能够缩减打包体积的手段
前端工程相关的内容都有哪些?
关于webpasck的优化
1.文件配置说明
这张图,清楚的告诉我们,webpack在什么阶段调用什么配置项,咱们可以借用这张图重新认识下webpack。
entry:是webpack的打包入口 output:是webpack的打包出口,就是把打包好的文件放到哪里去。 一般配置如下:
const path = require('path')
module.exports = {
entry: {
app: path.resolve(__dirname, '../../src/app.js'),
},
output: {
filename: 'js/[name].[hash:8].js',
path: path.resolve(__dirname, '../../dist'),
},
}
解释下filename: 'bundle.js',直接规定好打包后的文件名字是bundle.js。
-
你也可以写成这样,
filename: '[name].js',打包的入口名字叫什么,打包后的名字就叫什么。 -
你也可以写成这样,
filename: '[id].js',每个包都有一个唯一id,我们就用这个id命名。 -
你也可以写成这样,
filename: '[contenthash].js',每个包都有一个缓存的hash值,我们就用这个hash命名。
webpack的hash值,很有用,对理解webpack-dev-server很有用,因为它的hot更新就是利用hash做的
**Hash 值类型:**
- `[hash]`:与整个项目的构建相关的哈希值。不论哪个文件有变化,整个项目构建的哈希值都会改变。
- `[chunkhash]`:与 Webpack 打包过程中生成的 chunk 相关的哈希值。同一 chunk 内文件没有变化时,[chunkhash] 是不变的。它可以用来优化浏览器缓存,因为不同的文件通常被打包进不同的 chunk。
- `[contenthash]`:与文件内容直接相关的哈希值。只有文件内容改变了,`[contenthash]` 才会改变。这在使用如 css-extract-plugin 分离 CSS 文件时非常有用,因为你可能只希望在 CSS 文件的内容实际发生变化时才改变文件名。
- `[modulehash]`:与单个模块相关的哈希值。
- `[fullhash]`:Webpack 5 引入的哈希值,用来代替旧版本中的 [hash]。
2.环境配置说明
由于不同操作系统设置获取环境变量的方式是不一样的,在 Mac 上,我们通常使用 export NODE_ENV=development ,而在Windows上,我们使用的是 set NODE_ENV=development 为了抹平系统差异带来的麻烦,我请cross-env 出手解决。
npm i cross-env -D
package.json里面添加:
"start": "cross-env NODE_ENV=development webpack",
在webpack.config.js里面我们拿一下环境变量看看
const env = process.env.NODE_ENV;
console.log(env, 999);
const isDev = process.env.NODE_ENV !== 'production';
3.Source Map,devtool 配置说明
- eval:每个模块会用 eval() 执行,并且在末尾追加一个 //@ sourceMappingURL 注释,最快。
- cheap-eval-source-map:生成较快,并且可以映射到行号。
- cheap-module-eval-source-map:生成较快,并且会生成 Source Map 到模块级别。
- eval-source-map:转换(transpile)每个模块,并在一个 DataUrl 中提供 Source Map,提供质量更好但速度较慢的 Source Map。
- cheap-source-map:不包含列信息的 Source Map,不包括 loader 的 Source Map。
- cheap-module-source-map:不包含列信息,但是加载器(loader)的 Source Map 会被简化为每行一个映射(mapping)。
- inline-source-map:生成一个完整的 Source Map,并以 DataUrl 的形式追加到输出文件末尾。
- source-map:生成一个独立的 Source Map 文件,提供完整的映射信息,通常用于生产环境中。
- hidden-source-map:创建 Source Map,但不在打包文件中引用,错误信息不会显示原始代码的位置信息,只会显示构建后代码的位置信息。
- nosources-source-map:创建 Source Map,但不包含 sourcesContent(原始代码内容)。 这些选项提供了不同层次的 Source Map 质量和构建性能之间的权衡。
一般情况下,在开发环境中,你可能会选择一个构建速度快的 Source Map 选项,比如 eval-source-map,以提高构建和重构建的速度。在生产环境中,为了获取好的源映射,通常会采用 source-map 或 hidden-source-map 这种完整但较慢的选项。
开发环境
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
})
生产环境
module.exports = merge(common, {
mode: 'production',
devtool: false,
})
如果你想调试生产环境的代码,请看这个文章# 作为前端开发,如何调试线上代码?
配置更新日志changelog
安装
npm i conventional-changelog-cli -D
在package.json里面配置
"scripts": {
...
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
执行命令
npm run changelog
生成文件
从此,你可以把你要提交的内容全部写在这个文件里面,以后我们要看看之前哪个分支做了什么事情,就一目了然了。从此你就可以快乐的回退代码了。
显示打包进度
安装
npm i progress-bar-webpack-plugin -D
配置webpack.config.js
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
new ProgressBarPlugin({ complete: '█', }),
执行命令npm run build
webpack性能优化
我对两层面分别做出6个总共12个性能优化建议,为了方便记忆都使用四字真言概括。⏱表示减少打包时间,📦表示减少打包体积。
- 减少打包时间:
缩减范围、缓存副本、定向搜索、提前构建、并行构建、可视结构 - 减少打包体积:
分割代码、摇树优化、动态垫片、按需加载、作用提升、压缩资源
1缩减范围
利用loader的include/exclude缩小搜索范围
2.利用缓存
很多loader和Plugin都会又自己的缓存配置项,目的就是在在下次打包的时候,像打补丁一样,谁没有编译过,就编译谁,以前编译过的文件复用就好。比如babel-loader的cache,还有eslint-webpack-Plugin的cache,都是缓存
3.定向搜索
resolve提高文件的搜索速度的配置项, 一般我们的别名,后缀都在这里配置。
extension可以设置扩展名,当我们写import App from './App'的时候,它自动会去找对应扩展名是[".js", ".ts", ".jsx", ".tsx", ".json", ".vue"]的文件。
alias可以设置别名,当我们写 import getName from '@/utils/user'的时候,它会把@帮我解析成src//utils/user
4.提前构建
webpack5的 DllPlugin|DllReferencePlugin
DefinePlugin主要功能
- 定义环境变量:可以根据不同的环境(如开发环境、生产环境)定义不同的变量,以便在代码中进行相应的处理。
- 配置参数动态化:将一些配置参数定义为全局变量,方便在代码中进行读取和使用。
- 条件编译:通过定义不同的变量来实现条件编译,从而减少不必要的代码执行。
说白了,他就是给webpack定义全局变量的工具,比如定义下面这个
new webpack.DefinePlugin({
ENV: JSON.stringify(process.env.NODE_ENV),
});
在src/index.js下面
测试
打包如果出现这个提示:
解决办法:
performance: {
maxEntrypointSize: 50000000,
maxAssetSize: 30000000,
},
DllPlugin 和 DllReferencePlugin
DLLPlugin能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。
首先需要新建一个webpack.dll.config.js文件。webpack.dll.config.js作用是把所有的第三方库依赖打包到一个bundle的dll文件里面,还会生成一个名为 manifest.json文件。
manifest.json的作用是用来让 DllReferencePlugin 映射到相关的依赖上去的。
DllReferencePlugin作用是把刚刚在webpack.dll.config.js中打包生成的dll文件引用到需要的预编译的依赖上来。
因为webpack.dll.config.js中打包后比如会生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含所有的第三方库文件,vendor-manifest.json文件会包含所有库代码的一个索引,当在使用webpack.config.js文件打包DllReferencePlugin插件的时候,会使用该DllReferencePlugin插件读取vendor-manifest.json文件,看看是否有该第三方库。vendor-manifest.json文件就是有一个第三方库的一个映射而已
第一次使用 webpack.dll.config.js 文件会对第三方库打包,打包完成后就不会再打包它了,然后每次运行 webpack.config.js文件的时候,都会打包项目中本身的文件代码,当需要使用第三方依赖的时候,会使用 DllReferencePlugin插件去读取第三方依赖库。所以说它的打包速度会得到一个很大的提升。
配置
1.先创建webpack.dll.conf.js
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
entry: {
// 项目中用到该两个依赖库文件
vendor: ['react', 'react-dom', 'antd', 'lodash-es'],
},
output: {
filename: '[name].dll.js', // 文件名称
path: path.resolve(__dirname, 'public'),// 将输出的文件放到public目录下
/*
存放相关的dll文件的全局变量名称,比如对于react来说的话就是 _dll_react, 在前面加 _dll
是为了防止全局变量冲突。
*/
library: '_dll_[name]'
},
plugins: [
new DllPlugin({
/*
该插件的name属性值需要和 output.library保存一致,该字段值,也就是输出的 manifest.json文件中name字段的值。
比如在react.manifest文件中有 name: '_dll_react'
*/
name: '_dll_[name]',
path: path.join(__dirname, 'public', '[name].manifest.json')/* 生成manifest文件输出的位置和文件名称 */
}),
]
};
2.配置命令
"dll": " webpack --config webpack.dll.config.js"
3.执行命令:npm run dll
4.使用DllReferencePlugin
在webpack.config.js里面配置
const { DefinePlugin, DllReferencePlugin } = require("webpack");
new DllReferencePlugin({
manifest: require('./public/vendor.manifest.json')
}),
5.AddAssetHtmlPlugin
配置以后,我发现public的包和dist包,不在一起,生成的index.html在dist里面,总不能在dist里面引用public包里面的东西吧,一看就不合理,最好的办法就是把public里面的东西拷贝到dist里面。手动拷贝,肯定是所有程序员最厌恶的事情,万一错了,忘了,咋办?所以出现了一个工具是add-asset-html-webpack-plugin,它会做两件事:1.帮我们把public里面的文件拷贝到dist里面,2.把dist里面拷贝过来的js添加到html文件里面。
在webpack.config.js文件里面
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
......
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 DefinePlugin({
'ENV': JSON.stringify('production')//定义全局变量
}),
new DllReferencePlugin({
manifest: require('./public/vendor.manifest.json')
}),
// 因为我们生成的缓存代码是在dll文件夹,我们要借助以下插件把dll下的文件帮运到dist文件夹下面
new AddAssetHtmlPlugin({
publicPath: ".",
outputPath: "../dist",
filepath: path.resolve(__dirname, "./public/vendor.dll.js")
}),
],
添加命令
"dev": "npx cross-env NODE_ENV=development webpack-dev-server ",
"build": "npx cross-env NODE_ENV=production webpack",
"lint": "eslint --fix \"./src/**/*.{js,jsx,ts,tsx}\"",
"prepare": "husky",
"lint:lint-staged": "lint-staged",
"commit": "git-cz",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dll": "npx cross-env NODE_ENV=production webpack --config webpack.dll.config.js"
6.测试
执行npm run dll 后再执行 npm run build 后你会发现
执行npm run dev
5.并行构建
配置Thread将Loader单进程转换为多进程
先来看下thread-loader吧,这个也是webpack4官方所推荐的。HappyPack的作者表示已不再维护此项目,这个可以在github仓库看到。所以happyPack就不要用了,直接选择thread-loader。
安装
npm i thread-loader -D
配置
const Os = require("os");
....
{
test: /\.(js|jsx|tsx|ts)$/,
include: path.resolve(__dirname, 'src'),
exclude: path.resolve(__dirname, 'node_modules'),
use: [{
loader: "thread-loader",
options: { workers: Os.cpus().length }
}, {
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
],
cacheDirectory: true
}
}]
},
执行npm run build
如果文件多的话,效果会更加明显!
6.可视结构
打包速度分析
npm i speed-measure-webpack-plugin -D
配置
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
});
执行npm run build
打包体积分析
安装
npm i webpack-bundle-analyzer -D