# hello Webpack
本节主要介绍 Webpack 相关知识,聊聊 Webpack 的由来,以及为什么要使用 Webpack,通过两大利器:Loader 与 Plugins 进行讲解。
要想快速知道 Webpack 是什么,最好的方式就是通过官网去了解它。通过官方介绍,我们可以知道:webpack 是一个现代 JavaScript 应用程序的静态模块打包器 (module bundler)。
在最初,Webpack 并不被人熟知,它刚出现时,主打的优势是 Code Splitting,我们现在从官网也能看到对它的定义:
Code Splitting : 代码分离指将代码分成不同的包 / 块,然后可以按需加载,而不是加载包含所有内容的单个包。
什么时候 Webpack 才受人关注?2014 年,Instagram 的前端团队在一次大会上分享其内部前端页面加载性能优化,提到最重要的一点就是用到了 Webpack 的 Code Splitting。
这简直就是为 Webpack 好友助力了一波,之后形成了一个热潮。Webpack 的风口来了,很多公司纷纷使用 Webpack,并贡献了无数的 Plugin、Loader,你一刀,我一刀,明天 Webpack 就出道,果不其然,短短时间内,Webpack 被推上了高潮。
大家都用,我需要用吗?如果说你的应用程序非常小,没有什么静态资源,只需要一个 JS 文件就可以满足需求,这时使用 Webpack 并不是一个好的选择。至于你用与不用,得靠你自身评估~
# 两大利器
得益于 Webpack 扩展性强,插件机制完善,官方提供了许多的 Loader、Plugin,接下来通过问题,配合简单明了的 demo,给大家讲解这两大利器,在此之前,我们先全局安装一下 Webpack。
npm install webpack --save --dev | |
npm install webpack-cli --save --dev |
# Loader 模块打包方案
官方对 Loader 的介绍是:Webpack 可以使用 Loader 来预处理文件。这允许你打包除 JS 之外的任何静态资源。
在我看来, Loader 就是一种模块打包方案
,怎么理解?给大家科普一个知识点:Webpack 默认是知道如何打包 js 类型文件,但对于其他类型文件,它是不知道如何处理,我们得告诉它,对这种类型文件,打包的方案是什么。接下来,我们通过例子,帮助小伙伴们理解为什么我说它是一种方案。
我们新建一个 demo
文件夹,创建一个 index.js
文件,文件结构是这样的
├── demo
│ └── index.js
└──...
此时我们在 index.js
中写下这行代码
// index.js | |
const str = 'hello webpack'; | |
console.log(str); |
执行一下 npx webpack ./index.js
,意思就是对我们的 index.js 文件打包。
我们在终端可以看到,在不配置任何东西情况下,Webpack 也能够打包 JS 类型文件,这说明 Webpack 默认对 JS 文件是有一套打包方案的
接下来,我们将代码改成这样,引入我们的图片
// index.js | |
import myPdkAvatar from './cover.jpg'; | |
const str = 'hello webpack'; | |
console.log(str); |
同上,执行 npx webpack index.js
,此时会报错。对 jpg 类型的文件打包失败了
Webpack 很友好,它会告诉你,你需要一个 loader 去处理此文件类型。
官方提供了一种专门处理此类型的方案:file-loader,我们安装一下这个 loader
npm install --save-dev file-loader |
接着新增一个文件 webpack.config.js
,此时的文件结构是这样的
├── demo
│ ├── index.js
│ └── webpack.config.js
└──...
我们在配置文件中,添加一下对于 jpg 这种类型文件的处理方案
// webpack.config.js | |
module.exports = { | |
module: { | |
rules: [ | |
{ | |
test: /\.jpg$/, | |
use: [ | |
{ | |
loader: 'file-loader', | |
options: { | |
name: '[name]_[hash].[ext]', | |
outputPath: 'images/', | |
}, | |
}, | |
], | |
}, | |
], | |
}, | |
}; |
解读一下这段代码,意思就是:当遇到模块 (module) 时,进行规则 (rules) 匹配,如果匹配到 /\.jpg$/
类型的文件,就采用 file-loader
方案进行打包,并且配置了参数: name
与 outputPath
,意味着打包后的文件名是按照 [文件名]_[哈希值].[源类型]
规则命名,并且输出在 images/
目录下
理解了这段代码含义之后,我们再来打包,看看结果如何,执行 npx webpack ./index.js
打包正常!我们再看看打包之后的 dist 文件下,是不是真的有个 images/
目录存放着打包后的图片?
如我们所想,现在回过头细品,Loader 就是一种模块打包方案是不是也有点道理?下面写几行代码,大家细品细品
module.exports = { | |
module: { | |
rules: [ | |
{ | |
test: /\.css$/, | |
use: ['style-loader', 'css-loader', 'postcss-loader'], | |
}, | |
{ | |
test: /\.less$/, | |
use: { | |
loader: 'less-loader', | |
}, | |
}, | |
{ | |
test: /\.vue$/, | |
use: { | |
loader: 'vue-loader', | |
}, | |
}, | |
], | |
}, | |
}; |
# Plugins 打包更加便捷
继续以上边的 Loader demo 为例子,回顾一下我们现在 demo 的文件目录结构
├── demo
│ ├── index.js
│ └── webpack.config.js
└──
我们先来执行一下 npx webpack index.js
,来看看 dist 目录下有哪些文件
通过官网可知,在我们未配置
output
属性时,它的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
因为我们都用的默认配置,所以打包生成的文件夹名就叫 dist,bundle 默认名称就是 main.js
接下来我们手动创建一个 HTML,加载打包后的 js 文件,如何加载呢?通过 script 加载打包后的 main.js
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>webpack plugins demo</title> | |
</head> | |
<body> | |
<div id="root"></div> | |
<!-- 在这里加载打包好之后的 main.js 文件 --> | |
<script src="./dist/main.js"></script> | |
</body> | |
</html> |
然后运行此页面,通过控制台,可以看到会打印出: hello webpack
假设现在有一种场景,需要通过 hash 进行命名输出的 bundle。我们来修改一下 webpack.config.js
module.exports = { | |
// 1. webpack 执行构建的第一步将从 entry 开始,这里我们的入口文件为 index.js | |
entry: './index.js', | |
// 2. 经过一系列处理得到最终的代码,然后输出结果 | |
output: { | |
// 这里将输出的结果代码文件自定义配置文件名 | |
filename: '[webpack]_[hash].bundle.js', | |
}, | |
// ... | |
}; |
执行 npx webpack ./index.js
,来看看打包之后的文件命名格式是否如我们预期
没毛病,这时候我们 HTML 加载该怎么办?手动修改成正确的文件地址
<script src="./dist/[webpack]_04638f451cfea56abd38.bundle.js"></script> |
如果我们将 index.js 文件中的内容修改(👇 下面添加一行代码)
// index.js | |
import myPdkAvatar from './cover.jpg'; | |
const str = 'hello webpack'; | |
console.log(str); | |
// 新添加的代码 | |
console.log('new add code ......'); |
然后把 dist 目录删除,再打包一次,看看文件 hash 是否一致?
Entrypoint main = [webpack]_b8a2cb97e0f716e38925.bundle.js |
通过对比,我们发现,每次修改,重新打包生成的 bundle 文件名哈希值都不一样。等价于每次打包都需要手动修改 HTML 中的文件引用。
太原始太麻烦了,低效率!为此,Webpack 提供了 Plugins 插件能力,让 Webpack 变得更加灵活。
官方提供了很多 Plugins,让我们的打包更加便捷,上面的问题,我们可以通过 HtmlWebpackPlugin 插件进行简化 HTML 文件的创建,这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用!
多说无益,上手试试,先根据文档,安装一下插件,看看它能实现怎样的效果
npm install --save-dev html-webpack-plugin |
安装好之后,我们来修改 webpack.config.js
内容,将这个插件引入
const path = require('path'); | |
// 👇 引入此插件 | |
const HtmlWebpackPlugin = require("html-webpack-plugin"); | |
// webpack.config.js | |
module.exports = { | |
// 1. webpack 执行构建的第一步将从 entry 开始,这里我们的入口文件为 index.js | |
entry: "./index.js", | |
// 2. 经过一系列处理得到最终的代码,然后输出结果 | |
output: { | |
// 这里将输出的结果代码文件自定义配置文件名 | |
path: path.resolve(__dirname, 'dist'), | |
filename: "[webpack]_[hash].bundle.js", | |
}, | |
// 👇 使用此插件 | |
plugins: [new HtmlWebpackPlugin()], | |
module: { | |
rules: [ | |
{ | |
test: /\.jpg$/, | |
use: [ | |
{ | |
loader: "file-loader", | |
options: { | |
name: "[name]_[hash].[ext]", | |
outputPath: "images/", | |
}, | |
}, | |
], | |
}, | |
], | |
}, | |
}; |
执行一下 npx webpack ./index.js
,打包的出来的文件有哪些?
HtmlWebpackPlugin 会在打包结束后,自动帮我们生成一个 HTML 文件,同时把打包后的 bundle 自动引入。当我们内容修改,重新打包,生成的 HTML 也会随着每次编译导致哈希变化的 bundle 自动引入。
是不是很完美呢?不,我们采用火眼金睛瞧一瞧由 HtmlWebpackPlugin 生成的 HTML 文件,你会发现好像有些问题?是不是 body
下少了一些 DOM 节点(比如 Vue、React 都会有一个 id 为 app 的 DOM 元素),怎么办?这是该插件默认生成的,有没有办法生成我想要的 DOM 结构呢?
HtmlWebpackPlugin 提供了一个配置参数 template
,它允许你自定义 HTML 文件,以此文件为模版,生成一份一样的 HTML 并为你自动引入打包后的 bundle。
我们来动手实现一下,首先定义一份 “别具一格” 的 HTML 模版。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>我是 HtmlWebpackPlugin 的模版</title> | |
<style> | |
* { | |
margin: 0; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="root"> | |
<div id="pdk">PDK Demo</div> | |
</div> | |
</body> | |
</html> |
然后通过修改 webpack.config.js
配置,采用此模版为基础
const path = require('path'); | |
// 👇 引入此插件 | |
const HtmlWebpackPlugin = require("html-webpack-plugin"); | |
// webpack.config.js | |
module.exports = { | |
// 1. webpack 执行构建的第一步将从 entry 开始,这里我们的入口文件为 index.js | |
entry: "./index.js", | |
// 2. 经过一系列处理得到最终的代码,然后输出结果 | |
output: { | |
// 这里将输出的结果代码文件自定义配置文件名 | |
path: path.resolve(__dirname, 'dist'), | |
filename: "[webpack]_[hash].bundle.js", | |
}, | |
// 👇 使用此插件 | |
plugins: [new HtmlWebpackPlugin({ | |
template: './index.html', | |
})], | |
module: { | |
rules: [ | |
{ | |
test: /\.jpg$/, | |
use: [ | |
{ | |
loader: "file-loader", | |
options: { | |
name: "[name]_[hash].[ext]", | |
outputPath: "images/", | |
}, | |
}, | |
], | |
}, | |
], | |
}, | |
}; |
最后我们来瞧瞧,是否打包后自动生成的 HTML 文件结构跟我们的模版一致?
事实证明,确实一模一样。
官方还有很多精巧有用的 Plugins 插件,几乎每个插件目的都是出于让你的打包构建更加便捷。小伙伴们要善于使用搜索引擎去寻找所需的插件工具(官方插件或第三方 Plugins 插件)及解决问题的方法。
# 总结
- Loader 就是一种模块打包方案,换言之,它是一名具备文件类型转换的翻译员
- Plugins 用于扩展 Webpack 的功能,使得 webpack 变得极其灵活。
- Plugins 可以在 Webpack 运行到某个时刻,帮你做一些事情。其实 Plugins 很像生命周期函数,在 Webpack 运行到某个生命周期去做些事情。
如上述例子中,HtmlWebpackPlugin 就是在 Webpack 打包过程结束的生命周期时刻,去做了一些事情 —— 自动生成 HTML 文件,引入打包后的 bundle。
在比如 clean-webpack-plugin 第三方的插件,它其实就是在 Webpack 打包之前的生命周期时刻,去做了一些事情 —— 删除我们打包的目录
这两个 Plugins 相信项目中都会用到,回去翻一翻项目的配置,结合文档,在细品细品。