一、webpack 性能优化之 HRM
-
创建空文件夹,通过
npm init
命令初始化package.json
文件,通过npm install webpack webpack-cli -g
命令全局下载webpack
和webpack-cli
,通过npm install webpack webpack-cli -D
命令本地下载webpack
和webpack-cli
,通过npm i style-loader css-loader -D
命令下载style-loader
和css-loader
,通过npm i less less-loader -D
命令下载less
和less-loader
,通过npm i html-webpack-plugin -D
命令下载html-webpack-plugin
插件,通过npm i url-loader file-loader -D
命令下载url-loader
和file-loader
,通过npm i html-loader -D
命令下载html-loader
。 -
创建
src
文件夹,在里面创建css
、js
、imgs
和media
文件夹,以及index.html
。css
文件夹中放index.less
和字体图标资源样式,js
文件夹中放index.js
和print.js
,imgs
文件夹中放图片资源,media
文件夹放字体图标的相关资源,代码如下所示:
-
index.less
#box { width: 200px; height: 200px; background-image: url('../imgs/angular.jpg'); background-repeat: no-repeat; background-size: 100% 100%; }
-
index.js
// 引入 import print from './print.js'; import '../css/iconfont.css'; import '../css/index.less'; console.log('index.js 文件被加载') print() function add(x, y) { return x + y; } console.log(add(1, 2)); if (module.hot) { module.hot.accept('./print.js', function () { print(); }) }
-
print.js
// 引入 import print from './print.js'; import '../css/iconfont.css'; import '../css/index.less'; console.log('index.js 文件被加载') print() function add(x, y) { return x + y; } console.log(add(1, 2)); if (module.hot) { module.hot.accept('./print.js', function () { print(); }) }
-
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>开发环境</title> </head> <body> <h1>开发环境配置</h1> <span class="iconfont icon-icon-test"></span> <span class="iconfont icon-icon-test2"></span> <span class="iconfont icon-icon-test3"></span> <span class="iconfont icon-icon-test1"></span> <div id="box"></div> <img src="./imgs/vue.jpg" alt="vue"> <img src="./imgs/react.png" alt="react"> </body> </html>
- 创建
webpack.config.js
文件,通过require
引入path
和html-webpack-plugin
,entry
是入口文件,output
是出口文件,filename
是打包输出后的文件名,path
是输出路径。module
是loader
的配置,rules
是详细的loader
配置。第一个规则是处理less
资源,匹配以.less
结尾的文件,使用的loader
是style-loader
、css-loader
和less-loader
。第二个规则是处理css
资源, 匹配以.css
结尾的文件,使用的loader
是style-loader
和css-loader
。第三个规则是处理图片资源,匹配以jpg
、png
和gif
结尾的文件图片资源,使用的loader
是url-loader
,进行options
配置,limit
是限制图片的大小小于8kb
,就会被base64
处理,给图片做优化,name
给图片做重命名,取hash
值的前10
位,取文件的原来扩展名,关闭es6
模块化,输出路径为imgs
。第四个规则是 处理html
中的img
资源,匹配以.html
结尾的文件,使用的loader
是html-loader
。由于处理图片资源采用的是ES6 module
解析,而html
文件中的img
资源使用commonJS
解析,会造成冲突,需要关闭ES6 module
。第五个规则是处理其它资源,使用exclude
不包括上面的所有资源,html|css|js|less|jpg|png|gif
,使用file-loader
,通过options
配置name
的值为取hash
值的前10
位,取文件的原来扩展名,通过outputPath
设置输出路径为media
。plugins
里面是一些插件配置,通过new HtmlWebpackPlugin()
,复制里面的文件,并自动引入打包输出的所有资源(JS/CSS
),设置mode
模式为development
开发模式。在devServer
中,contentBase
是项目构建后路径,compress
是启动gzip
压缩,port
是端口号,open
是自动打开浏览器,hot
是开启HRM
功能,代码如下所示:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule: false,
outputPath: 'imgs'
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true
}
};
HMR
是hot module replacement
,热模块替换,也可以说是模块热替换。作用是一个模块发生变化,只会重新打包这一个模块,而不是打包所有模块,极大提升构建速度,HRM
在样式文件、JS
文件和HTML
文件的不同应用:
- 样式文件:可以使用
HMR
功能,因为style-loader
内部实现了 JS
文件:默认不能使用HMR
功能,需要修改js
代码,添加支持HMR
功能的代码。注意的是,HMR
功能对js
的处理,只能处理非入口js
文件的其他文件,添加代码, 全局寻找module
这个变量,有没有hot
这个属性,一旦module.hot
为true
,说明开启了HMR
功能,方法会监听print.js
文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行后面的回调函数,如下所示:
if (module.hot) {
module.hot.accept('./print.js', function () {
print();
})
}
HTML
文件:默认不能使用HMR
功能.同时会导致问题,html
文件不能热更新了,不用做HMR
功能。如果需要做,修改entry
入口,将html
文件引入,代码如下所示:
entry: ['./src/js/index.js', './src/index.html']
- 在命令行输入
npx webpack-dev-server
命令,项目就会自动打包编译。每次在更改完代码后,自动编译看到最新的显示内容。在build
打包文件中,也可以看到打包后的css
、js
、imgs
和media
文件夹,以及index.html
。
二、webpack 性能优化之 source-map
- 在上面
HRM
热模块替换的项目中,修改webpack.config.js
文件,添加devtool
来指定不同的source-map
,代码如下所示:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule: false,
outputPath: 'imgs'
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true
},
devtool: 'eval-source-map'
};
source-map
是一种 提供源代码到构建后代码映射 技术,如果构建后代码出错了,通过映射可以追踪源代码错误,不同source-map
之间的比较,如下所示:
类型 | 类型描述 | 类型方式 |
---|---|---|
source-map | 错误代码准确信息和源代码的错误位置 | 外部 |
inline-source-map | 只生成一个内联source-map,错误代码准确信息和源代码的错误位置 | 内联 |
hidden-source-map | 错误代码错误原因,但是没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误位置 | 外部 |
eval-source-map | 每一个文件都生成对应的source-map,都在eval,错误代码准确信息 和源代码的错误位置 | 内联 |
nosources-source-map | 错误代码准确信息, 但是没有任何源代码信息 | 外部 |
cheap-source-map | 错误代码准确信息 和 源代码的错误位置,只能精确的行 | 外部 |
cheap-module-source-map | 错误代码准确信息 和 源代码的错误位置,module会将loader的source map加入 | 外部 |
- 内联和外部的区别:
- 外部生成了文件,内联没有
- 内联构建速度更快
-
开发环境需要速度快,调试更友好。在速度快中,
eval>inline>cheap>...
,而eval-source-map
优于eval-cheap-souce-map
。在调试友好中,cheap-souce-map
优于cheap-module-souce-map
。所以,在开发环境中,最快调试,调试更加友好,可以选择eval-source-map
或者是eval-cheap-module-souce-map
。 -
在生产环境中,需要考虑源代码的隐藏和调试的友好。内联会让代码体积变大,所以在生产环境不用内联。
nosources-source-map
是全部隐藏,hidden-source-map
是只隐藏源代码,会提示构建后代码错误信息。所以,在生成环境中,可以选择source-map
或者是cheap-module-souce-map
。 -
在命令行输入
npx webpack-dev-server
命令,项目就会自动打包编译。每次在更改完代码后,自动编译看到最新的显示内容。在build
打包文件中,也可以看到打包后的css
、js
、imgs
和media
文件夹,以及index.html
。
三、webpack 性能优化之 oneOf
-
创建空文件夹,通过
npm init
命令初始化package.json
文件,通过npm install webpack webpack-cli -g
命令全局下载webpack
和webpack-cli
,通过npm install webpack webpack-cli -D
命令本地下载webpack
和webpack-cli
,通过npm i html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
命令下载html-webpack-plugin
、mini-css-extract-plugin
和optimize-css-assets-webpack-plugin
,通过npm i eslint-loader eslint -D
命令下载eslint-loader
和eslint
,通过npm i eslint-config-airbnb-base eslint-plugin-import eslint -D
命令下载eslint-config-airbnb-base
、eslint-plugin-import
和eslint
,eslint-config-airbnb-base
依赖于eslint-plugin-import
和eslint
,通过npm i postcss-loeader postcss-preset-env -D
命令下载postcss-loeader
和postcss-preset-env
,通过npm i css-loader less-loader -D
命令下载css-loader
和less-loader
,通过npm i url-loader file-loader html-loader -D
命令下载url-loader
、file-loader
和html-loader
,通过npm i babel-loader @babel/core @babel/preset-env -D
命令下载babel-loader
、@babel/core
和@babel/preset-env
,通过npm i @babel/polyfill core-js -D
命令下载@babel/polyfill
和core-js
。 -
创建
src
文件夹,在里面创建index.js
、index.html
、a.css
和b.css
文件,代码如下所示:
- index.js
import '../css/a.css'; import '../css/b.css'; function add(x, y) { return x + y; } console.log(add(2, 5));
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>webpack</title> </head> <body> <h1>hello webpack</h1> <div id="box1"></div> <div id="box2"></div> </body> </html>
- a.css
#box1 { width: 100px; height: 100px; background-color: pink; }
- b.css
#box2 { width: 200px; height: 200px; background-color: deeppink; }
- 创建
webpack.config.js
文件,通过require
引入path
、html-webpack-plugin
、mini-css-extract-plugin
和optimize-css-assets-webpack-plugin
。通过process.env.NODE_ENV
定义nodejs
环境变量:决定使用browserslist
的哪个环境。定义一个复用loader
,在下面匹配.css
和.less
文件的时候,需要重复使用。通过MiniCssExtractPlugin.loader
提取css
单独文件,使用css-loader
,使用postcss-loader
做css
的兼容性处理,同时使用postcss-preset-env
做css
的按需加载的兼容性处理,options
是postcss-loader
的配置项,ident
是固定写法,plugins
是使用postcss-preset-env
这个插件。entry
是入口文件,output
是出口文件,filename
是输出的文件名,path
是文件的输出路径。module
是loader
的配置,rules
是详细的loader
配置。在rules
中使用oneOf
,以下的loader
只会匹配一个,不能有两个配置处理同一种类型文件,如果有需要提取出来。第一个规则是匹配以.css
结尾的文件,通过use
使用commonCssLoader
这个抽出复用的loader
。第二个规则是匹配以.less
结尾的文件,通过use
使用commonCssLoader
这个抽出复用的loader
和less-loader
。第三个规则是匹配以.js
结尾的文件,通过exclude
不包括node_modules
,通过enforce
优先执行,通过loader
使用eslint-loader
,通过options
设置自动修复eslint
的错误。第四个规则是匹配以.js
结尾的文件,通过exclude
不包括node_modules
,通过loader
使用babel-loader
,通过options
进行配置,presets
为预设,指示babel
做怎么样的兼容性处理,使用babel/preset-env
这个预设。通过useBuiltIns
按需加载,通过corejs
指定core-js
版本,通过targets
指定兼容性做到哪个版本浏览器。js
兼容性处理需要使用到babel-loader
、@babel/core
和@babel/preset-env
。如果是基本js
兼容性处理,需要使用@babel/preset-env
,但是只能转换基本语法,如promise
高级语法不能转换。如果使用全部js
兼容性处理,需要使用@babel/polyfill
,但是只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了,所有就需要做兼容性处理的就做做兼容性处理的就做,使用core-js
。第五个规则是匹配以.jpg
、png
和gif
格式的图片资源,通过loader
使用url-loader
,url-loader
依赖于file-loader
。进行options
配置,设置limit
为8 * 1024
,图片大小小于8kb
,就会被base64
处理,给图片做优化。这样做的优点是减少请求数量(减轻服务器压力),缺点是图片体积会更大(文件请求速度更慢)。设置esModule
为false
,由于url-loader
默认使用es6
模块化解析,而html-loader
引入图片是commonjs
,解析时会出问题为[object Module]
,所以关闭url-loader
的es6
模块化,使用commonjs
解析。设置name
为[hash:10].[ext]
,给图片进行重命名,因为图片值为hash
,[hash:10]
取图片的hash
的前10
位,[ext]
取文件原来扩展名。第三个规则是匹配以.html
结尾的文件,使用loader
为html-loader
,处理html
文件的img
图片(负责引入img
,从而能被url-loader
进行处理)。第六个规则是匹配以.html
结尾的文件,通过loader
使用html-loader
。第七个规则是匹配其它资源,通过loader
使用file-loader
,通过options
进行输出路径配置,输出文件夹为media
。plugins
里面是一些插件配置,通过MiniCssExtractPlugin
插件提取css
以及对输出的css
文件进行重命名,通过OptimizeCssAssetsWebpackPlugin
对css
文件进行压缩,通过HtmlWebpackPlugin
复制里面的文件,并自动引入打包输出的所有资源(JS/CSS
),进行minify
配置,通过collapseWhitespace
移除空格,通过removeComments
移除注释,压缩 html 文件。设置mode
为production
,在生产环境下webpack
会自动压缩js
代码,代码如下所示:
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
process.env.NODE_ENV = 'production'
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
module.exports = {
entry: 'src/js/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
}
- 在
package.json
这个文件中,进行配置browserslist
,在css
兼容处理的时候,需要帮postcss
找到package.json
中browserslist
里面的配置,通过配置加载指定的css
兼容性样式,development
为开发环境配置,last 1 chrome version
表示兼容最近的chrome
版本,last 1 firefox version
表示兼容最近的firefox
版本,last 1 safari version
表示兼容最近的safari
版本。production
为生产环境配置,>0.2%
表示大于99.8%
的浏览器,not dead
表示不要已经死的浏览器,not op_mini all
表示不要使用op_mini all
浏览器。eslint
并不知道要检查什么,所有就有airbnb
风格指南,airbnb
需要使用eslint-config-airbnb-base
这个库,这个库依赖于eslint-plugin-import
和eslint
。所有需要在package.json
中eslintConfig
中设置检查规则,设置extends
为airbnb-base
,代码如下所示:
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base"
}
- 在命令行输入
webpack
命令,资源就会进行打包,以及相应的压缩代码。