Webpack基本使用
# 什么是 Webpack
webpack 的核心概念是一个模块打包工具,它的主要目标是将 js 文件打包在一起,打包后的文件用于在浏览器使用,但它也能胜任转换(transform)、打包(bundle)、包裹(package)任何其他资源。
# 追本溯源
在面向过程阶段,会把 js 代码写在同一个 js 文件中,代码不容易理解和维护。
在面向对象开发阶段,把代码分模块书写再统一引入,这样解决了面向过程开发中的问题,但出现了新的问题:
- 每个模块都需要引入一个 js 文件,随着模块增多,这会影响页面性能;
- 在 index.js 文件,即引入的入口文件,并不能直接看出模块的逻辑关系,必须去页面才能找到;
- 引入 js 的顺序必须严格按照顺序引入,因为 js 文件之间可能会进行 DOM 操作,顺序不能调换,否则会报错。
在现代开发模式,前端开发进一步工程化,采用模块化加载方案,包括:ES Module、AMD、CMD 和 CommonJS 等等。常用的前端模块化方案有 ES Module、Common JS。
但是浏览器并不能直接识别 ES Module 代码,需要借助其他工具来进行翻译,于是 Webpack 的出现就是为了解决这个问题。
# Webpack 安装与使用
包括全局安装和本地安装(推荐)。本地安装只在当前项目下有效,避免项目在不同电脑下因为版本不一样而不能正常打包。
// 本地安装
npm install webpack webpack-cli -D
npm install webpack webpack-cli --save-dev
// 查看webpack的历史版本记录
$ npm view webpack versions
// 按版本号安装
$ npm install webpack@4.25.0 -D
2
3
4
5
6
7
8
9
10
# Webpack 基本参数配置与打包输出
webpack.config.js
是 Webpack 配置文件,基本配置参数如下:
- entry:webpack 打包的入口
- output:webpack 输出配置
- filename 选项用来配置打包后的文件名;
- path 选项配置打包后输出的目录文件夹。
改写 package.json 文件,添加执行脚本命令:
"scripts": {
"bundle": "webpack"
},
2
3
打包输出项如下:
- Hash:hash 代表本次打包的唯一 hash 值,每次打包此值都是不一样的;
- Version:Webpack 版本号;
- Time:本次打包耗时;
- Asset:打包出的文件名称;
- Size:打包出的文件大小;
- Chunks:打包后的 js 文件对应的 id,从 0 开始,依次递增;
- Chunks Names:打包后的 js 文件名字;
- Entrypoint main = bundle.js:代表打包入口为 main
- warning in configuration: 提示警告,需要配置 mode 属性
- development 开发环境
- production 生产环境
- none 都不是
- 默认为生产环境
# webpack 打包静态资源
# loader
对于静态资源,包括图片、css 等,Webpack 是怎么对它们进行打包的呢?是采用 loader,它是一种打包规则,告诉 Webpack 在遇到非 js 文件时候,应该如何处理这些文件。
loader 有如下固定运用规则:
- 使用 test 正则来匹配相应的文件
- 使用 use 来添加文件对应的 loader
- 对于多个 loader 而言,从右到左依次调用
- 使用 options 来增加额外的规则
- 使用 exclude 来排除匹配的文件
# loader 打包图片(file-loader 或 url-loader)
安装:npm install file-loader -D 或者 npm install url-loader -D
webpack.config.js 配置:
const path = require("path");
module.exports = {
// 其他配置
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: "file-loader",
// 文件占位符 分别为原本文件名字、唯一编码、原本文件后缀
options: {
name: "[name]_[hash].[ext]",
},
},
},
],
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# loader 打包 CSS
CSS 样式文件分为多种情况,每种都需要不同的 loader 处理:
- 普通 css 文件,使用 style-loader 和 css-loader
- less 文件,使用 less-loader
- sass 或 scss,使用 sass-loader
- styl 文件,使用 stylus-loader
打包 CSS 文件的话,需要:
安装:$ npm install style-loader css-loader -D
webpack.config.js 配置:
// path为Node的核心模块
const path = require("path");
module.exports = {
// ......
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"], // 从右到左的顺序调用,所以顺序不能错
},
],
},
};
2
3
4
5
6
7
8
9
10
11
12
13
# Webpack 核心配置(plugin 等)
插件 plugin 是 webpack 的亮点,当 webpack 运行到某一个阶段,可以用 plugin 来帮我们做一些事情。
# html-webpack-plugin
可以让我们使用固定的模板,在每次打包的时候自动生成一个.html 文件,并且它会自动帮我们引入打包后的 js 文件。
安装:$ npm install html-webpack-plugin -D
配置:
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: {
main: "./src/index.js",
},
plugins: [
new htmlWebpackPlugin({
template: "src/index.html",
}),
],
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# clean-webpack-plugin
在打包之前,自动删除 dist 打包目录及目录下所有文件,不需要手动删除。
安装:$ npm install clean-webpack-plugin -D
配置:
const cleanWebpackPlugin = require("clean-webpack-plugin");
module.exports = {
// ......
plugins: [new cleanWebpackPlugin()],
};
2
3
4
5
# sourceMap 配置
一种映射关系,映射了打包后的代码和源代码之间的对应关系,通过 devtool 属性来配置。主要用来代码维护和调试找错方面。
- 开发环境下(development):推荐将 devtool 设置成 cheap-module-eval-source-map
- 生产环境下(production):推荐将 devtool 设置成 cheap-module-source-map
# WebpackDevServer + HMR(模块热更新)
webpack-dev-server 可以在源代码更改的情况下,自动打包代码并启动一个小型服务器,与热更新一块使用能够帮助我们高效开发。
安装:
$ npm install webpack-dev-server -D
package.json 配置:
// 其它配置
"scripts": {
"bundle": "webpack",
"watch": "webpack --watch",
"dev": "webpack-dev-server'
}
2
3
4
5
6
- webpack.config.js 配置
module.exports = {
// 其它配置
devServer: {
// 以dist文件为基础启动一个服务器,服务器运行在4200端口上,每次启动时自动打开浏览器
contentBase: "dist",
open: true,
port: 4200,
hot: true, // 启用模块热更新
hotOnly: true, // 模块热更新启动失败时,重新刷新浏览器
},
};
2
3
4
5
6
7
8
9
10
11
HMR(模块热更新),能够在不刷新浏览器的前提下,运行时候能帮我们更新最新的代码,已内置在 Webpack 中了。
# 处理 ES6 语法
考虑低版本浏览器兼容性问题,需要把 ES6 代码转换成低版本浏览器能够识别的 ES5 代码。使用 babel-loader 和@babel/core 来进行 ES6 和 ES5 之间的链接,使用@babel/preset-env 来进行 ES6 转 ES5。
- 安装
// 安装 babel-loader @babel/core
$ npm install babel-loader @babel/core --save-dev
// 安装 @babel/preset-env
$ npm install @babel/preset-env --save-dev
// 安装 @babel/polyfill进行ES5代码补丁
$ npm install @babel/polyfill --save-dev
2
3
4
5
6
7
8
- webpack 配置
module.exports = {
// 其它配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
- .babelrc 文件
@babel/preset-env 需要在根目录下有一个.babelrc 文件,如下:
{
"presets": ["@babel/preset-env"]
}
2
3
# Webpack 进阶
# tree shaking
树摇,用于移除项目中未使用的代码,只适用于 ES Module 语法(export、import),依赖于原发的静态结构特性。
启用:
const path = require("path");
module.exports = {
mode: "production",
optimization: {
usedExports: true,
},
};
2
3
4
5
6
7
# 区分开发模式和生产模式
分别书写公用配置文件,开发环境配置和生产环境配置文件,通过webpack-merge
插件可以合并公共部分和私有部分。
# CodeSplitting
把较大文件,分离成更小的块,让浏览器进行并行加载,减轻大文件传输压力。配置如下:
module.exports = {
// 其它配置
optimization: {
splitChunks: {
chunks: "all",
},
},
};
2
3
4
5
6
7
8
chunks 属性:
- async:此值为默认值,只有异步导入的代码才会进行代码分割。
- initial:与 async 相对,只有同步引入的代码才会进行代码分割。
- all:表示无论是同步代码还是异步代码都会进行代码分割。
# SplitChunksPlugin
可以用来做上面的代码分割,还有其他配置参数:
module.exports = {
// 其它配置项
optimization: {
splitChunks: {
chunks: "async",
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: "~",
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Webpack 和浏览器缓存(hash 值)
为了防止浏览器端缓存了内容,而服务端对于文件内容进行修改后,浏览器端需要识别到,可以对文件进行 hash 标识,从而判断浏览器端缓存内容是否过期。
// 开发环境下的output配置还是原来的那样,也就是webpack.common.js中的output配置
// 因为开发环境下,我们不用考虑缓存问题
// webpack.prod.js中添加output配置
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
2
3
4
5
6
7
# 常见 webpack 面试题
# 1.常见的 loader
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件(图片、字体)
- url-loader:与 file-loader 相似,可以设置阈值
- babel-loader:ES6 转换为 ES5
- sass-loader:将 sass/scss 代码转换为 css
- css-loader:加载 css,支持模块化、压缩、文件导入
- style-loader:把 css 代码注入到 javascript 中,通过 DOM 操作去加载 css
- postcss-loader:扩展 css 语法,配合 autoprefixer 插件自动补齐 css3 前缀
# 2.常见的 plugin
- html-webpack-plugin:html 文件创建和引入 js
- clean-webpack-plugin:目录清理
- webpack-bundle-analyzer:可视化 webpack 输出文件的体积
- webpack-merge:提取公共配置,减少重复配置代码
# 3.loader 和 plugin 区别
loader 本质上是一个函数,对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 javascript,所以 loader 成了翻译官,对其他类型的资源进行转译预处理。在 module.rules 中配置,类型为数组,每一项都是 Object,内部包含 test、loader、options 等属性。
Plugin 是插件,可以扩展 webpack 功能,利用 webpack 运行的生命周期中会广播出许多时间,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 api 改变输出结果。类型是数组,每一项都是一个 plugin 实例,参数都通过构造函数传入。
# 4.Webpack 构建流程
- 初始化:启动构建,读取与合并配置参数,加载 plugin,实例化 compiler
- 编译:从 entry 出发,针对每个 module 串行调用对应的 loader 去翻译文件的内容,再找到该 module 依赖的 module,递归地进行编译处理;
- 输出:将编译后的 module 组合成 chuk,将 chunk 转换成文件,输出到文件系统中。
# 5.sourceMap 是什么,怎么用
source map 是将编译打包压缩后的代码映射回源代码的过程,方便调试源码。只要不打开开发者工具,浏览器是不会加载该文件。
# 6.HMR 热更新
HMR 特点是不用刷新浏览器便可以将新变更的模块替换掉旧的模块。
核心:客户端从服务端拉取更新后的文件,准确说是 chunk diff(chunk 需要更新的部分),实际上 WDS(webpack-dev-server)与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上次资源进行对比。
客户端对比出差异后会向 WDS 发起 ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 JSONP 请求获取该 chunk 的增量更新。
后续拉到增量更新内容后,由 HotModulePlugin 来完成,提供了相关 api 以供开发者针对自身场景进行处理。比如说 react-hot-loader 和 vueloader 都是借助这些 api 实现 HMR。
# ES6 模块与 CommonJS 模块差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口;
- CommonJS 模块的 require()是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。
参考资料:
从今天开始,学习 Webpack,减少对脚手架的依赖(上) (opens new window)