Webpack 是什么
在上一篇文章中介绍了构建工具是如何产生构建工具前世今生,而 webpack 是当前使用人数最多,生态环境最好的构建工具。
webpack 以一个或多个文件作为入口进行打包,最终将整个项目所有文件以一个或多个文件的形式进行输出,输出的是编译好的文件,可直接在浏览器中运行。
Webpack 基本使用
项目构建
首先,我们介绍一下 webpack 的基本使用,在之后的文章中对相关模块进行详细的介绍。
创建项目并下载依赖 webpack 及 webpack-cli。
mkdir webpack-demo
cd webpack-demo
npm install -y
npm install webpack webpack-cli -D
这里需要说明一下 webpack 与 webpack-cli 的区别,两者概念并不相同。
- webpack: 实际执行打包的工具,将多个文件(如JavaScript、CSS、图像等)打包成单(多)个文件。
- webpack-cli: 与Webpack进行交互的命令行工具,它提供了一些命令,以便于在终端中运行Webpack.
接着创建用于测试打包的代码:
向 package.json 中的 script 添加 命令,用于执行 webpack 打包:
终端中执行命令,可以看到 index.js 文件成功被打包,可以证明项目构建是成功的。
你可能会有疑问,这打包后的文件和我直接写的代码有啥需求,我直接引入不就好了,为啥还要用webpack打包。别急,webpack 强大的功能将在后面马上介绍到。
webpack 基本概念
由于我们使用的是 webpack5,所以并不需要做配置就可以将 index.js 文件进行打包,这是因为 webpack5 有了默认的配置。在实际项目中,往往是需要配置指定配置文件的,从而完成所需。
我们在项目跟目录中创建 webpack.config.js 文件,webpack 打包时,会先读取该文件中的配置。
// webpack 是基于 node.js 运行的,所以采用的是 commonJS 规范
module.exports = {
// 入口
entry: '',
// 出口
output: {},
// 模块
module: {
rules: []
},
// 插件
plugins: [],
// 模式
mode: '',
}
- entry: 指定 webpack 开始进行打包的入口文件
- output: 指定 webpack 将打包后的文件输出位置以及文件命名等
- loader: webpack 只能识别 js、json,对于其他资源,如 vue、react 等,都需要对应的 loader 进行转换,将其转换为 webpack 可识别等资源
- plugin: 用于扩展 webpack 的功能
- mode: 模式,开发模式、生产模式。
根据上面的概念,我们可以设置 webpack 的配置文件如下:
const path = require('path');其实
module.exports = {
// 入口
entry: './src/index.js',
// 出口
output: {
// 编译后文件的存放目录
path: path.resolve(__dirname, 'dist'),
// 编译后文件的明明
filename: 'bundle.js'
},
// 模块
module: {
rules: []
},
// 插件
plugins: [],
// 模式
mode: 'development',
}
处理样式资源
处理 css 资源
webpack 作为静态资源打包文件,其是可以对 CSS 进行打包的,但是 webpack 并不认识css 资源,需要借助 loader 才能够进行解析。
终端执行命令,进行打包。此时打包失败,同时 webpack 也告诉了我们,我们需要一个 loader 处理 css 的文件。
处理 css 资源,需要借助 css-loader 以及 style-loader。同时,需要在 webpack 的配置文件中,指定 css 资源需要使用对应的 loader 处理。
npm install css-loader style-loader -D
// webpack.config.js
module.exports = {
...
// 模块
module: {
rules: [
{
// 匹配到 css 文件
test: /\.css$/,
// 先使用 css-loader 处理 css 文件,然后将处理后的结果再使用 style-loader 处理
use: ['style-loader', 'css-loader']
}
]
},
}
此时,再次执行打包命令,可以看到打包成功。
为了验证打包后,css 是否生效,我们在dist目录下创建一个 html 文件,并引入打包后的文件。
可以看到样式成功渲染到网页中,需要注意的是 webpack中 loader 是从右向左进行执行的。对于上述的配置来说,先执行 css-loader,再执行 style-loader,不能将位置放反。
我们简单介绍下,css-loader 以及 style-loader 是如何进行工作的。
- css-loader:会将CSS文件中的样式代码进行解析和转换,并将每个样式声明转换成一个JavaScript对象,如下所示:
{
color: 'red',
fontSize: '20px',
// ...
}
- style-loader: 将
css-loader输出的JavaScript对象转换成一个style标签,并将其插入到HTML文档中。
处理 less 资源
为了提升开发效率,css 预处理 less、sass 以及后处理器 postcss 在项目中被大规模应用。对于 less、sass 等资源,同样需要借助对应的 loader 才能进行解析后,才能被 webpack 处理。这里我们演示一下webpack 是如何进行 less 资源的处理,处理 less 资源使用 less 及 less-loader 依赖。
npm install less less-loader -D
这里我们猜测一下,less 资源的loader 应该怎么写?
首先,需要将 less 资源转换为 css 资源,然后使用 css-loader 将 css 样式转换为 js 对象,最后再使用 style-loader 将对应的 js 对象转换为 style 标签并插入 html 中。 webpack 中 loader 的执行顺序是从右向左执行,所以对应的配置如下:
module.exports = {
...
// 模块
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
}
你可能会有这样的疑问,为啥要安装 less-loader 和 less,只安装 less-loader 不可以嘛?这里,我们简单介绍一下 less-loader 的工作原理。
-
读取 Less 文件内容。
-
将 Less 代码编译为 CSS 代码。这一步需要使用 less 库进行编译(这也就是我们安装 less 库的原因)。
-
将编译后的 CSS 代码输出到 Webpack 中。
-
Webpack 将 CSS 代码处理为 CSS 模块,并将其添加到打包后的输出文件中。
在如上的内容中,我们介绍了如何使用 webpack 处理 css 以及 less 资源,如果我们想要处理 sass、postcss 的样式资源,采用同样的原理,借助对应的 loader 进行处理,这里不做过多的赘述了。
处理图片资源
处理图片资源的步骤同处理 css 资源,即借助对应的 loader 解析。在 webpack4 中,处理图片资源使用 file-loader 和 url-loader 进行处理。
在 webpack5 中,已经内置了这两个 loader,只需进行简单配置即可。
module.exports = {
...
// 模块
module: {
rules: [
...
{
test: /\.(png|jep?g|gif|webp|svg)/,
// asset 表示静态资源,webpack 会使用自动使用 file-loader、url-loader 进行处理
type: 'asset',
}
]
},
// 插件
plugins: [],
// 模式
mode: 'development',
}
可以看到,打包后dist 目录多了一个hash值命名的图片,并且打开html后,对应的图片资源在页面中成功被渲染。
实际开发中,对于一些比较小的图片资源,我们采用 base64 的方式进行渲染,从而减少请求次数。webpack 同样提供了该功能,只需做如下配置:
{
test: /\.(png|jep?g|gif|webp|svg)/,
type: 'asset',
parser: {
dataUrlCondition: {
// 小于 20 kb 的图片,采用 base64 的方式进行引入
maxSize: 20 * 1024
}
}
}
由于打包后的静态资源与其他代码处于同一级目录,并且文件命名不可读,我们可通过如下配置修改文件存储目录及设置文件命名。
{
test: /\.(png|jep?g|gif|webp|svg)/,
type: 'asset',
parser: {
dataUrlCondition: {
// 小于 20 kb 的图片,采用 base64 的方式进行引入
maxSize: 10 * 1024
}
},
generator: {
// 静态资源存储在 static/images 目录
// 文件命名为 文件名-hash取6位.扩展名
filename: 'static/images/[name]-[hash:6][ext]'
}
}
重新打包后,静态资源被输出至指定目录下,并且文件命名符合我们预期。但是为啥会有两个图片文件?webpack 并不会自动清除上一次构建产生的资源。
处理 JS 资源
webpack 虽然能够处理 JS 资源,但是其对于 JS 资源的处理非常有限,并不能进行代码兼容性处理。
项目开发中,我们经常用到 ES6-ES10 之间的语法,但是低版本的浏览器可能并不能支持这些语法,此时就需要进行代码降级,我们使用 babel 进行代码降级。
npm install babel-loader @babel/core @babel/preset-env -D
项目的根目录中创建一个 .babelrc 文件来指定配置转换规则
{
"presets": ["@babel/preset-env"]
}
指定处理 js 资源的 loader:
{
test: /\.js/,
exclude: /node_modules/, // 排除 node_modules 下的js 资源
loader: 'babel-loader',
}
处理 TS 资源
处理 TypeScript 需要借助 ts-loader,处理步骤如下所示:
npm install typescript ts-loader -D
项目跟目录下创建 tsconfig.json 文件:
// 用于指定 ts 编译时相关的配置
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"strict": true
}
}
配置 webpack.config.js 处理 ts 资源:
module.exports = {
...
// 模块
module: {
rules: [
{
test: /\.ts/,
exclude: /node_modules/, // 排除 node_modules 下的js 资源
loader: 'ts-loader',
}
]
},
resolve: {
extensions: ['.ts']
},
}
webpack 处理 ts 资源时,还另外将“.ts”添加到resolve.extensions数组中,告诉Webpack在尝试解析导入语句时也要查找TypeScript文件。如果没有将“.ts”添加到resolve.extensions中,Webpack就无法正确解析TypeScript模块,这会导致编译错误或运行时错误。
处理 HTML 资源
在上面的演示中,我们手动创建了一个 html 文件,并将打包后的资源进行手动引入。webpack 为我们提供 html-webpack-plugin 插件,该插件可以自动生成 html 文件(或手动指定 html 文件),然后自动引入打包后的资源。
使用步骤如下:
npm install html-webpack-plugin -D
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
// 插件
plugins: [
new HTMLWebpackPlugin(),
],
}
自动清除上一次构建
webpack 并不会自动清除上一次的构建产物,每次手动删除右比较麻烦,我们可以通过 clean-webpack-plugin 来实现该功能。
npm install clean-webpack-plugin -D
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
...
// 插件
plugins: [
new HTMLWebpackPlugin(),
new CleanWebpackPlugin()
],
}
开发服务器
通过上述的介绍,我们已经了解了 webpack 如何处理常见的资源,并且处理这一资源相对来说效率提升了很多。但是我们每改一行代码,都需要重新编译,然后再刷新页面,还是比较麻烦。
为此,webpack 提供了开发服务器,每修改一次代码,都能够进行自动编译以及模块热加载。同时,在本地开发中,往往需要访问线上的服务器,此时会存在跨域问题,而 webpack-dev-server 则可以解决跨域,极大提高开发效率。
npm install webpack-dev-server -D
// package.json
"scripts": {
"build": "webpack",
"dev": "webpack server --open"
},
module.exports = {
...
devServer: {
port: 8888
},
}
生产模式
生产模式下,webpack 会进行 JS 代码的压缩从而提高效率。除此之外,我们还需要进行其他配置,进一步提高效率。
可以看到 JS 在生产环境下被压缩为一行了。
CSS 单独打包
开发环境下,CSS 文件是被打包到 JS 文件中的。这会导致如下问题:
1、JS 文件过大,网络请求时间长,会出现闪屏现象
2、项目结构不清晰
我们可以通过 mini-css-extract-plugin 插件,将 css 资源单独打包成一个文件。使用 MiniCssExtractPlugin 时,需要将所有的 style-loader 替换为 MiniCssExtractPlugin.loader
npm install mini-css-extract-plugin -D
CSS 兼容性处理
有些 css 新语法在旧的浏览器中可能不支持,或者需要增加特定前缀才能支持。此时我们需要使用 CSS 后处理器,postcss 进行处理。
npm install postcss-loader postcss postcss-preset-env -D
CSS 压缩
CSS 压缩使用 css-minimizer-webpack-plugin 插件。
npm install css-minimizer-webpack-plugin -D
总结
如上便是 webpack 基本使用的内容,我们了解了常见资源的处理。目前而言,项目中我们主要是用 Vue 以及 React,由于这两种框架都提供了基于 webpack 的脚手架,所以未做介绍,但是处理原理依然和上面一致。
最后,我们了解到 webpack 分为生产模式和开发模式,生产模式下,我们需要进行一定的优化,提升页面到效率。你可能会问,开发环境下为什么不做这些优化?有如下原因
1、代码压缩后,可读性差,有时我们可以需要看编译后的代码。
2、代码进行优化会增加耗时,开发阶段我们更看重看法效率。