文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 !
** 一、Webpack 自动刷新**
HMR
模块热替换:应用过程中实时替换某个模块,而应用运行状态不受影响。HMR 极大程度的提高了开发者的工作效率,因此很受欢迎。
开启HMR
HMR集成在webpack-dev-server中,无需再单独安装模块。
1.直接在命令中,使用 --hot 开启热更新
yarn webpack-dev-server --hot
2.在 webpack.config.js 中,进行配置开启热更新
const webpack = require('webpack')
module.exports = {
mode: 'development',
devServer: {
hot: true
},
plugins: [
// 载入 webpack 的内置插件
new webpack.HotModuleReplacementPlugin()
]
}
Webpack 中的 HMR 并不可以开箱即用,他需要手动处理 JS 模块热替换逻辑。
HMR APIs
在自己的代码中使用这套API处理当某一个模块更新过后应该如何替换掉当前运行的页面当中。
在 main.js 中,使用 HMR APIs 手动处理热替换:
// module.hot 对象,是 HMR API 的核心对象
/**
* accept(),用于注册模块更新过后的处理函数
* 第一个参数,指的是 依赖模块的路径
* 第二个参数,指的是 依赖路径模块更新过后的处理函数
*/
module.hot.accept('./editor', () => {
// 热替换逻辑
})
1.处理 HMR 的代码报错会导致自动刷新
const webpack = require('webpack')
module.exports = {
devServer: {
hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
},
plugins: [
// 载入 webpack 的内置插件
new webpack.HotModuleReplacementPlugin()
]
}
2.启用 HMR 的情况下,HMR API 报错
main.js:
if (module.hot) {
// 先判断这个对象是否存在,再进行任务的注册
module.hot.accept('./editor', () => {
// 热替换逻辑
})
}
3.业务中添加的处理代码,会在打包过后自动移除。
二、Webpack 不同环境下的配置
不同的工作环境创建不同的webpack配置。
1.配置文件根据环境不同导出不同配置,在webpack.config.js中,进行导出配置:
const webpack = require('webpack')
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
/**
* 导出一个函数,在这个函数中设置所需的配置对象
* 接收两个参数,
* 第一个参数 env:即 通过 cli 传递的环境名参数
* 第二个参数 argv: 指 运行 cli 过程中传递的所有参数
*/
module.exports = (env, argv) => {
// 设置默认模式:开发模式
const config = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
}
// 生产模式
if (env === 'production') {
config.mode = 'production'
config.devtool = false // 禁用 Source Map
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
return config
}
启动开发模式,默认模式为 开发模式,无需指定工作模式
yarn webpack
启动生产模式,使用 --env 指定工作模式
yarn webpack --env production
注意:只适用中小型项目的配置,大型项目不适合。
一个环境对应一个配置文件
1.新建 webpack.common.js,用来存放 公共 配置信息:
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 公共配置
module.exports = {
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
})
]
}
2.安装webpack-merge 模块,合并webpack配置
yarn add webpack-merge --dev
3.新建 webpack.prod.js ,用来存放 生产模式 配置信息
// 导入 webpack-merge
const merge = require('webpack-merge')
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')
// webpack-merge模块 导出 merge(), 合并 webpack 配置
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
4.新建 webpack.dev.js ,用来存放 开发模式 配置信息
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
5.运行时,可以直接使用 --config 参数指定配置文件
$ yarn webpack --config webpack.prod.js
6.或者 在 package.json 中,配置 NPM Scripts
{
"scripts": {
"dev": "webpack --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
}
}
三、Webpack 配置优化
DefinePlugin:为代码注入全局成员。在 production 模式下,默认启用,会为代码中注入一个 process.env.NODE_ENV 的常量。一般通过这个成员去判断当前的运行环境,从而去执行对应的操作。
基本使用:在webpack.config.js中,进行配置
const webpack = require('webpack')
module.exports = {
// ...
plugins: [
// DefinePlugin是 webpack 的内置插件
// 构造函数接收一个对象,对象中的每一个键值都会被注入到代码中
new webpack.DefinePlugin({
// 值要求的是一个 JS代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
}
Tree -shaking
摇掉代码没有用的部分,即未引用代码。在启动生产模式时,自动检测出未引用的代码,并自动将未引用代码移除,从而减少冗余代码。Tree-shaking 并不是指某个配置选项,它是一组功能搭配使用后的优化效果,在 production 模式下自动开启。
基本使用
不使用 production 模式,其他模式下开启 Tree-shaking。
1.在webpack.config.js中,进行配置
module.exports = {
// 其他代码省略
mode: 'none',
// 集中配置 webpack 内部的优化功能
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能将所有模块合并并输出到到一个函数中,既提升了运行效率,又减少了代码的体积。
concatenateModules: true,
// 压缩输出结果
minimize: true
}
}
Tree-shaking 前提是 ES Modules,也就是说,由 webpack 打包的代码必须使用 ES Modules。
2.最新的 babel-loader 默认关闭 ESM 转换,可以在 webpack.config.js 中,手动设置关闭。
webpack.config.js:
module.exports = {
mode: 'none',
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
// 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
// ['@babel/preset-env', { modules: 'commonjs' }]
// 配置为 false,确保 preset-env 内部不会开启 ES Module 转换的插件
// ['@babel/preset-env', { modules: false }]
// 使用默认配置:auto,这样 babel-loader 会自动关闭 ESM 转换
['@babel/preset-env', {
modules: 'auto' }]
]
}
}
}
]
},
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输出结果
// minimize: true
}
}
sideEffects
sideEffects,webpack 4 中新增的特性,它允许通过配置的方式去标识代码是否有副作用,从而为 Tree-shaking 提供更大的压缩空间,在 production 模式下自动开启。副作用,是指模块执行时除了导出成员之外所做的事情。
适用场景:
sideEffects,一般用于 npm 包标记是否有副作用。
前提条件:
确保你的代码真的没有副作用,否则会误删掉那些有副作用的代码。
1.在webpack.config.js中,开启sideEffects特性
module.exports = {
mode: 'none',
optimization: {
// 手动开启特性
sideEffects: true,
// 模块只导出被使用的成员
// usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输出结果
// minimize: true,
}
}
检查package.json中是否有sideEffects的标识,以吃来判断这个模块是否有副作用。
2.设置所有模块都没有副作用,模块中没有被用到的代码就不会再被打包,则将会被移除掉。
package.json:
{
"sideEffects": false
}
3.设置某些模块具有副作用,即 将这些模块会被打包进输出结果,不会被移除掉。
package.json:
{
"sideEffects": [
"./src/extend.js",
"*.css"
]
}
Code splitting
Code Splitting,代码分包/代码分割,在应用中按需加载模块,从而提高应用的响应速度和运行效率。
多入口打包
多入口打包,一般适用于多页应用程序,即 一个页面对应一个打包入口,公共部分单独提取。
基本使用:
1.在 webpack.config.js 中,进行配置
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
/**
* entry 属性值的形式:
* 字符串:设置一个打包入口
* 数 组: 将多个文件打包进一个文件中
* 对 象: 设置多个打包入口,分别生成对应的打包文件
* 一个属性对应一个打包入口,
* 属性名为入口名称,属性值为入口所对应的文件路径
*/
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
// 多个入口,就意味着生成多个打包文件
// 使用 [name] 占位符,动态输出文件名
// [name] 最终会被替换成 入口的名称
filename: '[name].bundle.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
// 指定每个 HTML 文件所使用的 bundle文件
// 每个打包入口会形成独立的 chunks
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
2.在 webpack.config.js 中,配置属性,使其在打包时自动提取公共模块
// 其余部分省略
module.exports = {
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
}
动态导入
按需加载,需要用到某个模块时,再加载这个模块,可以极大的节省带宽和流量。动态导入的模块会被自动提取到对应的 bundle 中,从而实现分包。相对多入口打包,更加灵活。
基本使用:在需要导入的地方,使用 ES Modules 的动态导入。
index.js:
if (hash === '#posts') {
// 魔法注释:命名 bundle 的名称,相同的名称会打包到一个 bundle 中
// /* webpackChunkName: 'components' */'
import(/* webpackChunkName: 'components' */'./posts/posts').then(({
default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
import(/* webpackChunkName: 'components' */'./album/album').then(({
default: album }) => {
mainElement.appendChild(album())
})
}
MiniCssExtractPlugin
将 CSS 代码从打包结果中提取出来的插件,通过这个插件,可以实现 CSS 的按需加载。
基本使用:
1.安装插件模块
yarn add mini-css-extract-plugin --dev
2.在 webpack.config.js 中,进行导入和配置:
// 导入 mini-css-extract-plugin 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
//'style-loader', // 将样式通过 style 标签注入到页面中
// 实现样式文件通过 link 标签的方式注入
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
// 创建 MiniCssExtractPlugin 插件实例,自动提取 CSS 到单个文件中
new MiniCssExtractPlugin()
]
}
当样式代码所占内存较小时,不建议生成单个文件,此时减少请求次数,可能效果更好。
OptimizeCssAssetsWebpackPlugin
webpack 内置的压缩插件,只针对 JS 代码,对于其他的资源文件,都要使用对应的压缩插件进行压缩。OptimizeCssAssetsWebpackPlugin,压缩输出的 CSS 文件。
1.安装插件模块
yarn add optimize-css-assets-webpack-plugin --dev # css 压缩插件
yarn add terser-webpack-plugin --dev # webpack 内置的 JS 压缩插件
2.在 webpack.config.js 中,进行导入和配置:
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入 mini-css-extract-plugin 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 导入 optimize-css-assets-webpack-plugin
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
// 导入 webpack 内置的 JS 压缩插件
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
// 只有在 minimizer 特性开启时,才会运行其内部的插件
// 生产模式时,minimizer 特性自动开启,即下面插件才会工作
// 配置 minimizer 时,webpack 内部的压缩插件就会失效,需要手动添加
minimizer: [
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebpackPlugin()
]
}
}
Webpack 输出文件名 Hash
一般我们部署资源文件时,都会启用文件的静态资源缓存,这样的话对于用户的浏览器而言就可以缓存住我们应用程序中的静态资源,后续就不需要再请求服务器得到这些静态资源文件了,整体我们应用的响应速度就有一个大幅度的提升,不过开启静态资源的客户端缓存也会有一些问题, 如果说在缓存策略当中缓存失效时间过短的话,效果就不是特别明显,如果把过期时间设置的比较长,一旦说这个应用发生了更新重新部署过后,又没有办法及时更新到客户端,为了解决这个问题,建议在生产模式下,需要给输出的文件名当中hash值,一旦资源文件发生改变,我们的文件名称也可以跟着一起去变化,对于客户端而言,全新的文件名就是全新的请求,就么有缓存的问题,就可以把服务端的缓存策略时间设置的非常长,就不用担心文件更新过后的问题。
三种 Hash:
1.hash属于项目级别,即项目中的任何一个地方变化,都会导致打包时,Hash值全部改变。
webpac.config.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
output: {
filename: '[name]-[hash].bundle.js'
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]-[hash].bundle.css'
})
]
}
2.[chunkhash] 属于 chunk 级别,即在打包中,只要是同一路的打包,chunkhash 就是相同的。相比于 [hash],控制较精确一点。
webpack.config.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
output: {
filename: '[name]-[chunkhash].bundle.js'
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]-[chunkhash].bundle.css'
})
]
}
3.[contenthash] 属于 文件级别,根据输出文件的内容输出 hash值,即不同的文件就有不同的hash值,最适合解决缓存问题,通过 :number 指定hash的长度。
webpack.config.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
output: {
filename: '[name]-[contenthash:8].bundle.js'
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]-[contenthash].bundle.css'
})
]
}