这是我参与更文挑战的第2天,活动详情查看: 更文挑战
前言
本系列将分多篇文章,逐一介绍 webpack5 的使用。
本系列比较新手向,适合从来没有使用过 webpack 或略知一二的人看,不适合希望深入 webpack 的人看。
起步
安装 webpack
webpack 依赖 node 环境,若没有安装 node,请先移步安装 node。
先创建一个空白文件夹 test,输入以下命令,初始化项目。
npm init
输入命令后,会提示你输入一些信息,直接 enter 下一步即可。
test 文件夹里将会自动创建一个 package.json 文件,该文件是这个项目的描述文件,用来存储项目的一些信息,如:项目名称、版本号、运行脚本等。
安装 webpack 和 webpack-cli(如果是 webpack4+,需要安装 webpack-cli)
npm install --save-dev webpack webpack-cli
在 package.json 文件中,可以看到新增了 devDependencies,并且里面含有 webpack 和 webpack-cli 及对应的版本号,说明已经成功安装好 webpack。
基本配置
在根目录下创建一个 src 目录,src 目录下创建一个 js 目录,里面创建一个 index.js 入口文件。
index.js 文件写上测试代码。
console.log('这是一个入口文件')
在根目录下创建一个 build 目录,创建一个 webpack.config.js 文件,里面写上如下代码。
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/js/index.js'),
output: {
filename: 'main.js',
path: path.resolve(__dirname, '../dist')
}
}
entry 是用来写入口配置,output 是用来写出口配置。
path.resolve 方法用于生成绝对路径,这个方法有些难懂,可以理解为一个 cd 操作,先 cd 到第一个参数路径,再配合第二个参数生成绝对路径,可以看下此文章,当初,我也是看了这篇文章才恍然大悟。
这里的配置的用意就是:index.js 作为入口文件,构建后输出到 dist 目录下的 main.js 文件。
在 package.json 文件里的 script 添加一条 build 构建命令,如下:
{
...
"scripts": {
"build": "webpack --config ./build/webpack.config.js",
...
},
...
}
运行命令 npm run build,我们可以发现根目录多了一个 dist 目录,里面有一个 main.js 文件。
因为现在没有 html 载体,js 将不能在浏览器运行的,下面将进行添加 html。
html-webpack-plugin
安装 html-webpack-plugin,该插件用于管理 html,利用好此插件可以开发单页面应用或多页面应用。
npm install --save-dev html-webpack-plugin
用法一:单页面
在 webpack.config.js,引入 html-webpack-plugin,并进行对应配置。
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
// 新增 plugins 属性
plugins: [
new HtmlWebpackPlugin({
title: '首页'
})
]
}
运行构建命令:npm run build。 我们可以发现,dist 目录自动生成了一个 title 为“首页”的 index.html,并且引入了 main.js。
用法二:多页面
但有时候我们的页面并不是一个页面,可能还有其他页面。
假设还需要开发其他两个页面:header.html 和 footer.html。
我们在 src 目录创建 html 目录,里面创建三个文件 index.html、header.html、footer.html,注意这里也需要创建 index.html 文件,我们不再需要 webpack 为我们自动生成页面,而是以这几个文件为模板生成。
修改 webpack 配置,如下:
module.exports = {
entry: path.resolve(__dirname, '../src/js/index.js'),
output: {
filename: 'main.js',
path: path.resolve(__dirname, '../dist')
},
plugins: [
// new HtmlWebpackPlugin({
// title: '首页'
// }),
// 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/index.html'),
filename: 'index.html',
chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/header.html'),
filename: 'header.html',
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/footer.html'),
filename: 'footer.html',
}),
]
}
我们再 build 一下,可以发现 dist 目录下多了 header.html 和 footer.html。
但是如果 header.html 和 footer.html 也有自己的 js,应该怎么办?
我们还需要修改一下 entry 、output 和 HtmlWebpackPlugin 的配置。
先在 src/js 目录下创建 header.js 和 footer.js。
修改 webpack.config.js 里的 entry。
module.exports = {
// entry: path.resolve(__dirname, '../src/js/index.js'),
entry: {
main: path.resolve(__dirname, '../src/js/index.js'),
header: path.resolve(__dirname, '../src/js/header.js'),
footer: path.resolve(__dirname, '../src/js/footer.js'),
},
output: {
// filename: 'main.js',
filename: '[name].[fullhash].js', // 不再指定文件名,用 [name] 来输出原文件名
path: path.resolve(__dirname, '../dist')
},
plugins: [
// new HtmlWebpackPlugin({
// title: '首页'
// }),
// 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/index.html'),
filename: 'index.html',
chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/header.html'),
filename: 'header.html',
chunks: ['header'] // 添加 chunks
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/footer.html'),
filename: 'footer.html',
chunks: ['footer']
}),
}
build 一下,可以发现 dist 目录包含了 main、header、footer 等相关 js。
你可能会发现 js 文件名会有一串码,这串码是哈希码,是上面 output 里面的“[fullhash]”起作用,为什么要有加这个代码?
我们都知道浏览器是有缓存的,每次打包部署一个项目,如果 js 文件名或引入 js 链接没有改变,浏览器将会使用上一次缓存下来的 js 文件,这显然不是我们想要的,因此每次打包时我们都给资源文件(js、css、图片等)添加一串不同的哈希码,防止浏览器使用缓存文件。
自动清理 dist 目录
细心的人可能会发现之前生成 main.js 依然还在,如果我们每次在构建时都要手动清理一下 dist 目录,会很麻烦。
新版本做法
现在 webpack 5.20.0+,已经自带清理功能,只要配置一下 output 的 clean 即可。
output: {
// ...
clean: true, // 在生成文件之前清空 output 目录
},
旧版本做法
旧版本,可以进行安装 clean-webpack-plugin 解决这个问题。
npm i -D clean-webpack-plugin
webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
...
plugins: [
...
new CleanWebpackPlugin(),
]
}
可以发现我们每次构建时,dist 目录都会被删除,然后再构建出新的 dist 目录和对应的内容。
注意:这个项目将采用旧版本做法。
webpack-dev-server
webpack-dev-server 是一个开发服务器,可以实现 HMR(模块热替换),所谓的 MHR 就是它把构建出来的文件保存到运行内存中,确保运行速度,每当你的开发代码发生改变时,立刻重新构建一遍到内存中,且通知浏览器更新页面内容。
利用 webpack-dev-server,我们可以大大提高开发效率。
安装 webpack-dev-server
npm i -D webpack-dev-server
webpack.config.js
module.exports = {
...
devServer: {
port: 3000,
hot: true,
contentBase: '../dist' // 如果出错,请将 contentBase 替换为 static
},
}
如果上面的 contentBase 导致了报错,请改为 static。contentBase 是旧版本的写法,static 是新版本(大概是 webpack-dev-server 4+ )的写法。
package.json 添加脚本 dev
{
...
"scripts": {
"dev": "webpack server --config build/webpack.config.js --open",
...
},
...
}
运行命令 npm run dev
我们可以发现浏览器自动打开并加载我们的页面了,如果不想要浏览器自动打开,删掉 dev 脚本命令里的 --open 即可。
当然,我们也可以自己输入 http://localhost:3000 (端口要看实际部署的) 进行加载页面。
注意:有些技术博客,在脚本命令那里可能是这样写的。
"scripts": {
"dev": "webpack-dev-server --config build/webpack.config.js --open",
...
},
在 webpack-cli4(对应 webpack5,webpack-cli3 才是对应 webpack4)里,webpack-dev-server 命令一运行,会报无法找到模块 webpack-cli/bin/config-yargs 错误,因为 webpack-cli4 已经去除了模块 webpack-cli/bin/config-yargs,如果想用 webpack-dev-server 命令,则需要降级为 webpack-cli3,否则使用 webpack server 命令代替。
当时 webpack4 刚升级 webpack5 那会,我就发现了这个问题,但 webpack 文档也明显写着是使用 webpack-dev-server(估计没有及时更改),在百度也找不到解决方法,后面在 github 的 webpack 的 issue 发现了这个问题的解决方法。所以,大家如果发现问题,可以多点查或提 github 的 issue,这是个很不错的方法。
- 2021.9.18 补充 现在最新的版本,好像已经没有这个问题了,webpack-dev-server 和 webpack server 命令均可以用,可能是官方为了兼容,改回来了。不过建议在新版本里使用 webpack server 命令。
这个问题的相关讨论,具体可以看这两个网站:
- Cannot find module 'webpack/bin/config-yargs' - Stack Overflow
- Error: Cannot find module 'webpack-cli/bin/config-yargs' · Issue #2759 · webpack/webpack-dev-server (github.com)
完整代码
目录
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// entry: path.resolve(__dirname, '../src/js/index.js'),
entry: {
main: path.resolve(__dirname, '../src/js/index.js'),
header: path.resolve(__dirname, '../src/js/header.js'),
footer: path.resolve(__dirname, '../src/js/footer.js'),
},
output: {
// filename: 'main.js',
filename: '[name].[fullhash].js',
path: path.resolve(__dirname, '../dist')
},
devServer: {
port: 3000,
hot: true,
contentBase: '../dist'
},
plugins: [
// new HtmlWebpackPlugin({
// title: '首页'
// }),
// 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/index.html'),
filename: 'index.html',
chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/header.html'),
filename: 'header.html',
chunks: ['header']
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/html/footer.html'),
filename: 'footer.html',
chunks: ['footer']
}),
new CleanWebpackPlugin(),
]
}
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack server --config build/webpack.config.js --open",
"build": "webpack --config ./build/webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^4.0.0-alpha.0",
"html-webpack-plugin": "^5.3.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2",
}
}
系列文章
webpack5 的使用(零):概念
webpack5 的使用(一):起步
webpack5 的使用(二):多个环境配置
webpack5 的使用(三):加载 css
webpack5 的使用(四):加载资源文件
webpack5 的使用(五):babel 转译 js 代码
webpack5 的使用(六):优化