解锁 Webpack,看这篇就够了
关注“前端学苑” ,坚持每天进步一点点
「~解锁webpack篇~」
一、解锁webpack 基础篇
1、webpack 定义
webpack 是一个现代 JavaScript 应用程序的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。
2、webpack 的核心概念
1) 入口 (entry )指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
2) 输出 (output) 属性告诉 webpack 在哪里输出它所创建的 bundles ,以及如何命名这些文件,默认值为 ./dist
3) 模块转换器(loader)让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
4) 插件(plugins),插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量
3、初始化配置
新建一个文件夹,如: webpack-first (当然,你可以使用任意一个你喜欢的项目名)。推荐:掌握了webpack之后,根据自己的需求配置出来的,就是最佳配置。
mkdir webpack-demo // 创建文件夹
cd webpack-demo // 进入这个文件夹目录
npm init // 初始化项目,生成package.json
要使用 webpack,那么必然需要安装 webpack、webpack-cli:
npm install webpack webpack-cli -D
鉴于前端技术变更迅速,本篇文章基于 webpack 的版本号
├── webpack@4.41.5
└── webpack-cli@3.3.10
从 wepack V4.0.0 开始, webpack 是开箱即用的,在不引入任何配置文件的情况下就可以使用。
新建 src/index.js 文件,我们在文件中随便写点什么:
//index.js
class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const dog = new Animal('dog');
使用 npx webpack --mode=development 进行构建,默认是 production 模式,我们为了更清楚查看打包后的代码,使用 development 模式。
webpack 有默认的配置,如默认的入口文件是 ./src,默认打包到dist/main.js。
查看 dist/main.js 文件,可以看到,src/index.js 并没有被转义为低版本的代码,这显然不是我们想要的。
{
"./src/index.js":
(function (module, exports) {
eval("class Animal {\n constructor(name) {\n this.name = name;\n }\n getName() {\n return this.name;\n }\n}\n\nconst dog = new Animal('dog');\n\n//# sourceURL=webpack:///./src/index.js?");
})
}
4、将JS转义为低版本
将JS代码向低版本转换,我们需要使用 babel-loader。
首先安装一下 babel-loader
npm install babel-loader -D
此外,我们还需要配置 babel,为此我们安装一下以下依赖:
npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm install @babel/runtime @babel/runtime-corejs3
新建 webpack.config.js,如下:
//webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: ['babel-loader'],
exclude: /node_modules/ //排除 node_modules 目录
}
]
}
}
建议给 loader 指定 include 或是 exclude,指定其中一个即可,因为 node_modules 目录通常不需要去编译,排除后,有效提升编译效率。
需要说明:
loader 需要配置在 module.rules 中,rules 是一个数组。
loader 的格式为:
{
test: /\.jsx?$/,//匹配规则
use: 'babel-loader'
}
或者也可以像下面这样:
//适用于只有一个 loader 的情况
{
test: /\.jsx?$/,
loader: 'babel-loader',
options: {
//...
}
}
test 字段是匹配规则,针对符合规则的文件进行处理。
use 字段有几种写法
1)可以是一个字符串,例如上面的 use: 'babel-loader'
2)use 字段可以是一个数组,例如处理CSS文件,use: ['style-loader', 'css-loader']
3)use 数组的每一项既可以是字符串也可以是一个对象,当我们需要在webpack 的配置文件中对 loader 进行配置,就需要将其编写为一个对象,并且在此对象的 options 字段中进行配置,如:
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"]
}
},
exclude: /node_modules/
}
]
5、mode
将 mode 增加到 webpack.config.js 中:
module.exports = {
//....
mode: "development",
module: {
//...
}
}
mode 配置项,告知 webpack 使用相应模式的内置优化。
mode 配置项,支持以下两个配置:
1)development:将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamedModulesPlugin
2)production:将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。
6、在浏览器中查看页面
我们可以使用 html-webpack-plugin 插件来帮助我们完成这些事情。
首先,安装一下插件:
npm install html-webpack-plugin -D
新建 public 目录,并在其中新建一个 index.html 文件 (vscode快捷 !+ tab)
修改 webpack.config.js 文件。
//首先引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...
plugins: [
//数组 放着所有的webpack插件
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包后的文件名
minify: {
removeAttributeQuotes: false, //是否删除属性的双引号
collapseWhitespace: false, //是否折叠空白
},
// hash: true //是否加上hash,默认是 false
})
]
}
此时执行 npx webpack,可以看到 dist 目录下新增了 index.html 文件,并且其中自动插入了 <script> 脚本,引入的是我们打包之后的 js 文件。
更多 html-webpack-plugin配置项
如何在浏览器中实时展示效果
话不多说,先装依赖:
npm install webpack-dev-server -D
修改下咱们的 package.json 文件的 scripts:
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server",
"build": "cross-env NODE_ENV=production webpack"
},
在控制台执行 npm run dev,启动正常,页面上啥也没有,修改下我们的JS代码,往页面中增加点内容,正常刷新(也就是说不需要进行任何配置就可以使用了)。
不过呢,我们还是可以在 webpack.config.js 中进行 webpack-dev-server 的其它配置,例如指定端口号,设置浏览器控制台消息,是否压缩等等:
//webpack.config.js
module.exports = {
//...
devServer: {
port: '3000', //默认是8080
quiet: false, //默认不启用
inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式
stats: "errors-only", //终端仅打印 error
overlay: false, //默认不启用
clientLogLevel: "silent", //日志等级
compress: true //是否启用 gzip 压缩
}
}
7、devtool
devtool 中的一些设置,可以帮助我们将编译后的代码映射回原始源代码。不同的值会明显影响到构建和重新构建的速度。
对我而言,能够定位到源码的行即可,因此,综合构建速度,在开发模式下,我设置的 devtool 的值是 cheap-module-eval-source-map。
//webpack.config.js
module.exports = {
devtool: 'cheap-module-eval-source-map' //开发环境下使用
}
生产环境可以使用 none 或者是 source-map,使用 source-map 最终会单独打包出一个 .map 文件,我们可以根据报错信息和此 map 文件,进行错误解析,定位到源代码。
source-map 和 hidden-source-map 都会打包生成单独的 .map 文件,区别在于,source-map 会在打包出的js文件中增加一个引用注释,以便开发工具知道在哪里可以找到它。hidden-source-map 则不会在打包的js中增加引用注释。
8、如何处理样式文件
webpack 不能直接处理 css,需要借助 loader。如果是 .css,我们需要的 loader
先安装一下需要使用的依赖:
npm install style-loader less-loader css-loader postcss-loader autoprefixer less -D
简单说一下的配置:
style-loader 动态创建 style 标签,将 css 插入到 head 中.
css-loader 负责处理 @import 等语句。
postcss-loader 和 autoprefixer,自动生成浏览器兼容性前缀了,应该没人去自己徒手去写浏览器前缀了吧
less-loader 负责处理编译 .less 文件,将其转为 css
我们之间在 webpack.config.js 写了 autoprefixer 需要兼容的浏览器,仅是为了方便展示。推荐大家在根目录下创建 .browserslistrc,将对应的规则写在此文件中,除了 autoprefixer 使用外。
注意:
loader 的执行顺序是从右向左执行的,也就是后面的 loader 先执行,上面 loader 的执行顺序为: less-loader ---> postcss-loader ---> css-loader ---> style-loader
9、图片/字体文件处理
我们可以使用 url-loader 或者 file-loader 来处理本地的资源文件。url-loader 和 file-loader 的功能类似,但是 url-loader 可以指定在文件大小小于指定的限制时,返回 DataURL,因此,个人会优先选择使用 url-loader。
首先安装依赖:
npm install url-loader -D
在 webpack.config.js 中进行配置:
//webpack.config.js
module.exports = {
//...
modules: {
rules: [
{
test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240, //10K
esModule: false
}
}
],
exclude: /node_modules/
}
]
}
}
此处设置 limit 的值大小为 10240,即资源大小小于 10K 时,将资源转换为 base64,超过 10K,将图片拷贝到 dist 目录。esModule 设置为 false,否则,<img src={require('XXX.jpg')} /> 会出现 <img src=[Module Object] />
将资源转换为 base64 可以减少网络请求次数,但是 base64 数据较大,如果太多的资源是 base64,会导致加载变慢,因此设置 limit 值时,需要二者兼顾。
10、处理 html 中的本地图片
安装 html-withimg-loader 来解决咯。
npm install html-withimg-loader -D
修改 webpack.config.js:
module.exports = {
//...
module: {
rules: [
{
test: /.html$/,
use: 'html-withimg-loader'
}
]
}
}
11、入口配置
入口的字段为: entry
//webpack.config.js
module.exports = {
entry: './src/index.js' //webpack的默认配置
}
entry 的值可以是一个字符串,一个数组或是一个对象。
12、出口配置
配置 output 选项可以控制 webpack 如何输出编译文件。
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), //必须是绝对路径
filename: 'bundle.js',
publicPath: '/' //通常是CDN地址
}
}
13、每次打包前清空dist目录
我们需要插件: clean-webpack-plugin,安装依赖:
npm install clean-webpack-plugin -D
以前,clean-webpack-plugin 是默认导出的,现在不是,所以引用的时候,需要注意一下。另外,现在构造函数接受的参数是一个对象,可缺省。
//webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
//...
plugins: [
//不需要传参数喔,它可以找到 outputPath
new CleanWebpackPlugin()
]
}
现在你再修改文件,重现构建,生成的hash值和之前dist中的不一样,但是因为每次 clean-webpack-plugin 都会帮我们先清空一波 dist 目录。
14、热更新
1)首先配置 devServer 的 hot 为 true
2)并且在 plugins 中增加 new webpack.HotModuleReplacementPlugin()
//webpack.config.js
const webpack = require('webpack');
module.exports = {
//....
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin() //热更新插件
]
}
15、多页应用打包
有时,我们的应用不一定是一个单页应用,而是一个多页应用,那么如何使用 webpack 进行打包呢。为了生成目录看起来清晰,不生成单独的 map 文件。
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
login: './src/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js'
},
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html' //打包后的文件名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html' //打包后的文件名
}),
]
}
16、利用webpack解决跨域问题
假设前端在3000端口,服务端在4000端口,我们通过 webpack 配置的方式去实现跨域。
首先,我们在本地创建一个 server.js:
let express = require('express');
let app = express();
app.get('/api/user', (req, res) => {
res.json({name: '张三'});
});
app.listen(4000);
在 index.js 中请求 /api/user,修改 index.js 如下:
//需要将 localhost:3000 转发到 localhost:4000(服务端) 端口
fetch("/api/user")
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.log(err));
配置代理
修改 webpack 配置:
//webpack.config.js
module.exports = {
//...
devServer: {
proxy: {
"/api": "http://localhost:4000"
}
}
}
重新执行 npm run dev,可以看到控制台打印出来了 {name: "张三"},实现了跨域。
二、解锁webpack 优化篇
1、量化
有时,我们以为的优化是负优化,这时,如果有一个量化的指标可以看出前后对比,那将会是再好不过的一件事。
speed-measure-webpack-plugin
插件可以测量各个插件和loader
所花费的时间,使用之后,构建时,会得到类似下面这样的信息:
speed-measure-webpack-plugin 的使用很简单,可以直接用其来包裹
Webpack
的配置://webpack.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
2、exclude/include
我们可以通过 exclude、include 配置来确保转译尽可能少的文件。顾名思义,exclude 指定要排除的文件,include 指定要包含的文件。
exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。
//webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: ['babel-loader'],
include: [path.resolve(__dirname, 'src')]
}
]
},
}
3、cache-loader
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。
首先安装依赖:
npm install cache-loader -D
cache-loader 的配置很简单,放在其他 loader 之前即可。修改Webpack 的配置如下:
module.exports = {
//...
module: {
//我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
rules: [
{
test: /\.jsx?$/,
use: ['cache-loader','babel-loader']
}
]
}
}
4、抽离公共代码
抽离公共代码是对于多页应用来说的,如果多个页面引入了一些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。CommonsChunkPlugin已经被移除了。
抽离公共代码对于单页应用和多页应该在配置上没有什么区别,都是配置在 optimization.splitChunks 中。
//webpack.config.js
module.exports = {
optimization: {
splitChunks: {//分割代码块
cacheGroups: {
vendor: {
//第三方依赖
priority: 1, //设置优先级,首先抽离第三方模块
name: 'vendor',
test: /node_modules/,
chunks: 'initial',
minSize: 0,
minChunks: 1 //最少引入了1次
},
//缓存组
common: {
//公共模块
chunks: 'initial',
name: 'common',
minSize: 100, //大小超过100个字节
minChunks: 3 //最少引入了3次
}
}
}
}
}
5、CDN
对于静态资源的处理,放入CDN是一个很好的选择,webpack中配置CDN的方式如下:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash:8].js',
publicPath: 'http://static.xxxx.com/'
},
6、happypack
由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack 构建慢的问题会显得严重。文件读写和计算操作是无法避免的,那能不能让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度呢?
HappyPack 就能让 Webpack 做到这点,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
首先需要安装 happypack:
npm install happypack -D
修改配置文件:
const Happypack = require('happypack');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'Happypack/loader?id=js',
include: [path.resolve(__dirname, 'src')]
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css',
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')
]
}
]
},
plugins: [
new Happypack({
id: 'js', //和rule中的id=js对应
//将之前 rule 中的 loader 在此配置
use: ['babel-loader'] //必须是数组
}),
new Happypack({
id: 'css',//和rule中的id=css对应
use: ['style-loader', 'css-loader','postcss-loader'],
})
]
}
说明:当 postcss-loader 配置在 Happypack 中,必须要在项目中创建 postcss.config.js。
//postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')()
]
}
另外,当你的项目不是很复杂时,不需要配置 happypack,因为进程的分配和管理也需要时间,并不能有效提升构建速度,甚至会变慢。
7、HardSourceWebpackPlugin
HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。
配置 hard-source-webpack-plugin,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
首先安装依赖:
npm install hard-source-webpack-plugin -D
//webpack.config.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
//...
plugins: [
new HardSourceWebpackPlugin()
]
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
执行 $ npm run build --report 后生成分析报告如下
觉得本文对你有帮助?请分享给更多人
关注「前端学苑」加星标,提升前端技能