之前通过一些 webpack 的简单配置,项目可以使用 React 来编写基本组件了,但是每次编写组件,都需要重新执行webpack
命令打包,然后再手动在浏览器中打开页面才能看到开发结果,这是十分影响开发效率的,我们要的效果yarn start
启动项目运行,在开发过程中使用ctrl+s
保存文件,然后页面能自动更新,为此需要配置 HMR 和 webpack-dev-server
。
开发模式
如果要使用开发模式的功能,需要为 webpack 指定开发模式,因为执行webpack
命令的时候如果不指定模式,那么默认就是生产环境production
。
module.exports = {
mode: 'development',
};
针对不同的模式,webpack 使用相应模式的内置优化,例如开启一些内置的plugin
等,具体如下:
development
-
在
DefinePlugin
中注册全局环境变量属性process.env.NODE_ENV = "development"
-
启用
NamedChunksPlugin
-
启用
NamedModulesPlugin
production
- 在
DefinePlugin
中注册全局环境变量属性process.env.NODE_ENV = "production"
- 启用
FlagDependencyUsagePlugin
,用于标记当前模块中import
部分使用到的import
,后续可以删除无用的import
- 启用
FlagIncludedChunksPlugin
,用于为每个 chunk 引入的其它 chunks 添加 id,消除无用的 chunk - 启用
ModuleConcatenationPlugin
,将所有模块连接到一个闭包函数内,可以提高代码的执行效率,这个插件是开启 tree shaking功能必须启用的插件 - 启用
NoEmitOnErrorsPlugin
,在编译出现错误时,使用NoEmitOnErrorsPlugin
来跳过输出阶段。这样可以确保输出资源不会包含错误 - 启用
OccurrenceOrderPlugin
,这个插件以前叫OccurenceOrderPlugin
,通过模块调用次数给模块分配 ids,常用的 ids 就会分配更短的 id,使 ids 可预测,减小文件大小 - 启用
SideEffectsFlagPlugin
,副作用代码标记,配合package.json
的"sideEffects"
配置项使用,对于无副作用的代码,可以删除一些无用的export
导出 - 启用
TerserPlugin
,使用 terser 压缩 JS 代码
针对不同模式的配置
函数配置
webpack 支持多种配置类型,一般来说简单配置的话,都可以使用module.exports
导出一个 webpack 配置对象的方式来做,但是对于复杂业务情况,例如需要针对不同环境启用不同的loader
,plugin
等功能,这时候需要使用 webpack 的函数配置方式来解决。
webpack 函数方式的配置可以接受两个参数:
- 第一个参数是一个环境变量对象
env
,内置一些环境变量属性,webpack 命令行 环境配置 的--env
参数,可以允许你传入任意数量的环境变量。而在webpack.config.js
中可以访问到这些环境变量
// 执行webpack命令,并指定生产环境,并配置环境变量NODE_ENV
webpack --env.NODE_ENV=development --progress
- 第二个参数是一个 map 对象(
argv
),可以从argv
中获取配置项信息传递到 webpack 的配置中
// webpack.config.js
module.exports = function(env, argv) {
// 接受NODE_ENV环境变量
const NODE_ENV = env.NODE_ENV;
return {
// 判断执行命令时为webpack指定的环境模式
mode: env.production ? 'production' : 'development',
...
plugins: [
new webpack.optimize.UglifyJsPlugin({
// 通过argv传递参数
compress: argv['optimize-minimize']
})
]
};
};
不同的配置文件
如果不想都在一个webpack.config.js
中杂糅所有配置项,出于维护性更高的目的,可以使用不同的 webpack 配置文件,例如开发环境指定webpack.development.config.js
,并在执行webpack
命令的时候通过命令行接口参数--config
指定使用不同的配置文件。
// 指定使用配置文件,config后面串接配置文件路径
webpack --config webpack.config.js
// 指定config文件夹中的webpack.development.config.js文件
webpack --config config/webpack.development.config.js
不同模式的切换
如果直接在webpack.config.js
的mode
配置项中写死在 webpack 不同模式中自由的切换,一般来说有两种方式:
- 第一种是在执行
webpack
命令的时候,通过 webpack 提供的命令行接口参数--mode
指定模式
// 指定开发模式
webpack --mode=development
// 指定开发模式
webpack --mode=production
- 第二种方式也是通过 webpack 提供的命令行接口参数
--mode
指定模式,不过结合了npm-scripts在package.json
文件中配置命令,这样就能通过npm run xxx
或者yarn xxx
来指定 webpack 的模式了
{
...
"scripts": {
"start": "webpack --mode=development",
"build": "webpack --mode=production"
},
}
- 最后一种是推荐方式,上文说过,webpack 支持函数配置方式,函数可以接受使用命令行接口参数
--env
传递的环境变量对象,所以这种方式是通过在 npm-scripts 中配置启动命令,传递给 webpack 配置函数的env
环境对象,然后在函数内部通过env
判断当前所处的模式,再写入到mode
配置项中
// package.json的scripts命令配置
{
...
"scripts": {
"start": "webpack --env.NODE_ENV=development",
"build": "webpack --env.NODE_ENV=production"
},
}
module.exports = function(env){
const isDevelopment = env.NODE_ENV === "development";
const isProduction = env.NODE_ENV === "production";
return {
mode: isProduction ? "production" : isDevelopment && "development",
...
}
}
WDS
WDS,webpack-dev-server,webpack 开发服务器。根据 webpack 的介绍,webpack-dev-server 可以在本地开启一个简单的 web 服务器,并且具有 live reloading(实时重新加载) 功能,也可以使用 webpack 的观察模式来做到文件修改自动构建,但是观察模式无法在浏览器中自动刷新页面,为了看到修改后的实际效果,需要手动刷新浏览器。
webpack-dev-server 能做到自动打包文件,并且自动刷新浏览器页面。
首先安装 webpack-dev-server
yarn add webpack-dev-server -D
在webpack.config.js
中简单配置启用 webpack-dev-server
module.exports = {
...
devServer: {
open: true,
port: 9999,
compress: true,
writeToDisk: false,
},
}
同时修改 npm-scripts 中的start
配置
{
...
"scripts": {
"start": "webpack-dev-server --env.NODE_ENV=development",
...
},
}
接下来就可以执行yarn start
命令查看 webpack-dev-server 的效果了。
接下来测试一下修改自动刷新页面
关于 webpack-dev-server 的详细配置项还有很多,需要注意的是,部分配置项带见CLI only
的表示该配置项只能用在命令行中,不能在webpack.config.js
使用,见 —— 配置 - DevServer。
配置项 | 值类型 | default | 含义 | port | number | 8000 | 域名端口号 | compress | boolean | false | 为每个静态文件开启 gzip 压缩 | https | boolean | false | 启用 https,如果开启 https 需要自定义的证书,否则浏览器会报错 | open | boolean | false | 告诉 dev-server 在服务器启动后在系统默认浏览器中显示页面 | openPage | string | index.html | 指定打开浏览器要浏览的页面,默认是根目录的index.html | proxy | object | null | 这个配置那是相当有用,直接用途可以不通过服务端解决开发环境的跨域请求问题,文档见 —— http-proxy-middleware | contentBase | boolean | false | 指定提供给 devServer 的静态文件的路径 | watchContentBase | boolean | false | 监听 [devServer.contentBase ]选项提供的静态文件,启用后,文件更改将触发整个页面重新加载 | writeToDisk | boolean | false | 告诉 devServer 将自动打包的文件写入硬盘,写入的文件目录路径为webpack.config.js 中配置的output.path | stats | string | 采用stats 对象,控制在控制台中显示哪些信息,例如'errors-only' 只在发生错误或有新的编译时输出;与下面的 noInfo 或quiet 一起使用时,该选项无效。 | overlay | object | false | 出现编译器错误或警告时,在浏览器中全屏显示出来 | noInfo | boolean | false | 在终端隐藏 webpack 打包过程等信息,只在出错或者警告的时候才显示这些信息 | quiet | boolean | false | 在终端隐藏 webpack 的错误或警告等信息,只在控制台显示初始启动信息 | liveReload | boolean | true | 热重载,默认情况下,检测到文件更改时,devser 就将重新编译打包,然后刷新页面;如果启动 HMR,这个选项会被自动禁用 | inline | boolean | true | inline 模式会自动刷新页面,并且构建消息将出现在浏览器控制台中,如果设置为false ,那么页面内容将放在一个iframe 里调试,并且页面顶部会出现一个调试进度条,当组件更新时,看不到页面刷新的过程 | hot | boolean | false | 启用 HMR | hotOnly | boolean | false | hotOnly 将作为构建失败时的回退;如果将hotOnly 设置为true ,那么组件更新以后,浏览器不会自动刷新 |
---|
HMR
webpack-dev-server 默认情况下,会开启热重载liveReload
功能,检测到文件更改时,devser 就将重新编译打包,然后刷新页面;
对于代码量小的项目,重新编译的时间损耗不会太多,但是项目需要使用的组件,模块等越来越多,打包过程也会越来越慢,会越来越影响开发效率。
HMR,hot module replacement,模块热替换,是 webpack 提供的在本地开发环境的程序运行过程中,替换,添加和删除模块代码以后,无需重新加载整个页面,便能完成页面更新的功能。同时,当前页面的状态也能保存下来。
使用 HMR,无需安装额外的插件,只需要在 devserver 的配置项中启用hot:true
即可,当设置hot:true
时,将自动添加HotModuleReplacementPlugin
这个插件,无需其他配置。
module.exports = {
...
devServer: {
hot: true
},
}
成功启用 HMR 以后,浏览器的 devtool 一般会输出 HMR 的信息,例如
对 React 来说,在 HMR 里做的是重新引入 root component,然后重新渲染。因为 HMR 是对 root component 的热替换,所以替换之后 root component 和它内部的 component 的 state 都会丢失,但是对于保存在像 Redux store 等外部数据容器中的状态则可以保持。
React Fast Refresh
社区过去一直使用React Hot Loader作为热更新 React 组件的方案,但是 19 年的时候,React 团队将 React Native 的 fast refresh 功能移植出来,支持在 web 开发中使用。
对于React Fast Refresh,目前没有正式的介绍文档,其相关概念只存在于 GitHub 的 issue 讨论中 —— What Is Fast Refresh?
目前,社区已经有了支持 React Fast Refresh 的 webpack 插件 —— React Refresh Webpack Plugin
yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh -D
使用 React Fast Refresh,需要在webpack.config.js
中配置两步:
- 启用引入 ReactRefreshWebpackPlugin;
- 在
babel-loader
中启用react-refresh/babel
这个 plugin;
// 引入ReactRefreshWebpackPlugin
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = function(env){
const isDevelopment = env.NODE_ENV === "development";
return {
plugins: [
...
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
module: {
rules: [
{
test: /\.m?jsx?$/,
exclude: /(node_modules)/,
use: {
loader: "babel-loader",
options: {
...
plugins: [
...
isDevelopment && require.resolve("react-refresh/babel"),
].filter(Boolean),
},
},
},
],
},
}
}
这里filter(Boolean)
是一个小技巧,Array
的filter(callback)
方法本身是删除掉数组中不满足条件的元素,也就是它只会把调用callback
返回true
的元素放入到新生成的数组中;当构造函数Boolean
作为回调函数的时候,对传入Boolean
只有在非false
,undefined
,NaN
,0
,空字符串“”
的时候才会返回true
。
使用&&
语法的时候,那些不会在当前环境中使用的 plugin 或者 loader,它们也会返回false
,有时候这种情况是会报错的,于是就使用filter(Boolean)
过滤掉那些在当前环境中不使用的 plugin 或者 loader 了。
source map
什么是 source map
最初的 JS 代码都会经过压缩(minify)操作,来移除代码中非必要性的空格,注释,换行符等内容,以减小代码体积。因为 JS 是嵌入在 HTML 中的,需要先通过网络获取才能解析执行,减小代码体积有利于缩短网络请求时间,进而间接地缩短网页加载的时间。
后来又出现了代码混淆技术,通过将代码中的各种元素,如变量、函数、类的名字改写成无意义的名字,来提升 JS 在客户端的安全性。
这些操作虽然有利于提升客户端体验,但是不利于开发人员调试,于是就出现了 source map 这项技术,将源代码和压缩后的代码对应起来,通过开启 source map 就能很好的找到映射的源代码,从而方便调试。
source map 本质上就是一个以.map
为后缀名的 JSON 文件,里面写入了一些源文件和压缩文件的映射关系属性,例如
{
"version": 3, //版本
"file": "script.js.map", //source map文件名
"sources": [
//源文件名
"app.js",
"content.js",
"widget.js"
],
"sourceRoot": "/", //源文件路径
"names": ["slideUp", "slideDown", "save"], //包含源文件中所有变量和函数名称的数组
"mappings": "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..." //
}
根据 source map 文件,在压缩后的代码文件底部通过一个注释字段sourceMappingURL
写入 source map 文件的路径,告知浏览器我这个压缩文件有一个源代码文件映射可以用,例如
//# sourceMappingURL=/path/to/script.js.map
后来,在 JS 基础上衍生出来的语言,例如 JSX,TS,CoffeeScript 等也都能通过这样技术映射出来。一些浏览器也都内置了对 source map 的支持,例如 Chrome 可以在 devtool 的设置面板中开启 JS 和 CSS 的 source map。
如果用了 WDS,是不需要使用 source map 的,因为开发环境的 WDS 有直观的错误提示,尤其在 React 中配合React Fast Refresh使用更加强大,可以在页面测试一下,给定以下代码:
export default class extends Component {
state = {
value: '',
};
/*当输入的时候直接报错*/
handleChange = e => {
throw new Error('测试');
};
render() {
return (
<div>
<input value={this.state.value} onChange={this.handleChange} />
</div>
);
}
}
当输入出错的时候,浏览器页面立即弹出了如下的错误提示框
但是,source map 在生产环境中仍然有使用的必要性,对于上面的错误测试代码,当我们执行yarn build
打包代码以后,生成的目录如下:
dist
├─ favicon.ico
├─ index.html
└─ main.js
在浏览器中打开 html 页面,输入之后立刻就会报错,从 devtool 可以获知报错信息如下:
可以看到这样的报错信息指向的代码位置已经糊成一团了,如果是复杂的错误,这种代码错误定位屁用没有。
devtool
要在 webpack 中开启 source map,只需要一个配置项devtool
,可以传入指定的模式字符串,或者使用devtool:false
禁用它;如果省略devtool
配置项,也就是不会生成 source map 文件。
module.exports = {
...
devtool: isProduction ? "source-map" : false,
};
推荐在生产环境下使用devtool: "source-map"
,执行yarn build
重新打包一下,看到目录下多了一个.map
文件
dist
├─ favicon.ico
├─ index.html
└─ main.js
└─ main.js.map
这时候打开 html 页面测试输入报错,可以看到已经成功定位到了源代码的错误点,OK!
除了 webpack 自带的输出 source map 文件的功能,一些 loader 也会提供生成 source map 的配置选项,不过它们最终都依赖于devtool
配置项是否启用,例如css-loader
提供sourceMap
的配置项,可以为 CSS 文件生成 source map。
不过需要注意的是,生产环境如果配置devtool
选项,依据不同的devtool
,对构建速度的影响也不同,一般来说:
- 指定
devtool:source-map
可以详细追踪到错误信息的位置,但是出现错误可以在 devtool 中直接跟踪到源码,例如上图 - 指定
devtool:eval
可以显示错误位置,但是代码会是经过 babel 等编译过的代码,如果是 React 组件,大致也能分析出错误的代码位置
-
指定
devtool:eval-cheap-source-map
和devtool:eval
看起来差不多,依旧是编译过的代码 -
指定
devtool:eval-cheap-module-source-map
也能显示具体的源码位置,不过相对于devtool:source-map
构建速度会大幅减少,webpack 是推荐使用这个配置项 -
指定
devtool:eval-source-map
,eval-nosources-source-map
,eval-nosources-cheap-source-map
,eval-nosources-cheap-module-source-map
也都能显示源码位置
注意点
如果在 webpack 的配置项optimization.minimizer
中自定义terser-webpack-plugin
的相关配置,哪怕只写了一个初始化terser-webpack-plugin
的实例,也会对 webpack 开发环境生成的 source map 造成影响,所以必须保证开启terser-webpack-plugin
的sourceMap
配置项,如果这个没配置,devtool
也没指定,那么开发环境的代码映射就会直接映射到打包生成的 chunk 下,并不会映射到源代码。
const TerserPlugin = require('terser-webpack-plugin'); //压缩JS代码
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
sourceMap: true,
}),
],
},
};
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!