webpack入门到实战(上)
介绍
webpack
是一个开源的JavaScript
模块打包工具,它会将所有资源文件(js
,json
,less
,img
,…)都作为模块处理,其最核心的功能就是解决模块之间的依赖关系,把各个模块按照特定的规则和顺序组织在一起,最终合并成一个或几个.js
。
JavaScript
不像大多数程序语言(如C
、C++
、Java
)那样,开发者可以直接使用模块进行开发,如果工程中有多个.js
,我们只能通过script
标签一个个插入页面中(这跟最初的设计有关),随着JavaScript
的技术发展,这种方式就会出现很多缺点
- 需要手动维护加载顺序,难以辨别的依赖关系
- 每个标签都意味着一次服务器请求
- 每个标签顶层作用域都是全局作用域,没有处理的变量或函数声明会污染全局作用域
而使用模块打包解决了上述问题
- 通过导入导出可以清晰的看出模块的加载依赖关系
- 打包合并后可以减少资源文件的网络开销
- 多个模块之间作用域隔离
虽然在2015年,ECMAScript正式定义了JavaScript的模块标准,但在实际应用方面还需要等待一段时间,因为还无法使用 code splitting,而且目前我们使用的 npm 模块大多数还是 CommonJS 的形式浏览器是不支持的,再加上个别浏览器(IE:是在说我么)不兼容的问题等。
因此模块打包工具也应运而生,目前社区中比较流行的模块打包工具有 Webpack
、Parcel
、Rollup
等。这里,我们选择 Webpack
进行学习(它默认支持多种模块标准,包括 AMD、CommonJS、以及最新的 ES6 模块,而其他工具大多只能支持一到两种,当然这也是它启动项目慢的主要原因,既然它可以支持那么多模块,还要什么自行车)。
打包第一个应用
npm init
生成 package.json
,相当于 npm项目说明书,记录了项目名称、版本等信息。
npm install webpack webpack-cli -D
其中,webpack
是核心模块,webpack-cli
则是命令行工具
// src/index.js
import addContent from './add-content.js'
document.write('My first Webpack app. <br />')
addContent()
// src/add-content.js
export default function () {
document.write('I am saying Hello world!')
}
配置打包指令
// package.json
...
"scripts": {
"build": "webpack --entry=./src/index.js --mode=development"
},
...
执行
npm run build
注意:因为
webpack
是本地安装的,因此无法直接在命令行内使用webpack xxx
指令,在工程内部只能使用npx webpack <command>
形式来执行指令,scripts
是 npm 提供的脚本命令功能,如果直接在命令行输入指令应该是npx webpack --entry=./src/index.js --mode=development
会找到我们当前入口文件 index.js
和其引用的其他模块,并通过他们来生成最终默认产物 dist/main.js
,然后我们手动将其引入到我们的html文件中
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My first Webpack app.</title>
</head>
<body>
<script src="./dist/main.js"></script>
</body>
</html>
浏览器直接打开index.html
webpack.config.js
1、 默认配置
- 默认入口是
src/index.js
,也就是entry
指定的值 - 默认输出目录是
dist/main.js
,如果要修改文件名可以使用--output-filename=xxx
指定,修改文件夹名可以使用--output-path=xxx
指定
// package.json
...
"scripts": {
// 表示入口是 ./src/index.js,输出为 ./dist/bundle.js
"build": "webpack --output-path=build --output-filename=bundle.js --mode=development"
},
...
2、 配置文件
通过上述添加脚本命令来对webpack进行配置没问题,但是当项目需越来越多的配置时,往命令里添加参数就会越来越长,不好看也不好维护。为了解决这个问题,可以把参数改为对象的形式专门放在一个配置文件里,webpack打包的时候读取这个配置文件并根据内容进行打包。
默认配置文件名为webpack.config.js
(修改配置文件名需要命令行指令参数指定),现在我们修改上面的配置为配置文件的形式
// package.json
...
"scripts": {
"build": "webpack"
},
...
// webpack.config.js
// 所有构建工具都是基于Node平台运行的,所以模块化默认采用commonJS
module.exports = {
mode: 'development',
// 入口文件
entry: './src/index.js',
output: {
// 配置文件的 path 要求必须是使用绝对路径
// 不写就是默认dist目录,也可以指定目录
// __dirname 是 Node 的变量,代表当前文件路径
path: path.join(__dirname, 'build'),
filename: 'bundle.js',
// 打包时删除原本成果物内容
clean: true
}
}
npm run build
<!-- index.html -->
<body>
<script src="./build/bundle.js"></script>
</body>
浏览器直接打开index.html
3、 打包结果
当使用mode=development
打包后的.bundle.js
内容
当使用mode=production
打包后的.bundle.js
内容
对比不同模式下的打包结果,生产模式下去除了注释,且对代码进行了压缩处理(成果物更小)。
Loader和plugin打包不同文件
loader 用于对模块的源代码进行转换,将文件从不同的语言转换为 JavaScript。
plugin 目的在于解决 loader 无法实现的其他事,使得 webpack 的功能更强大
loader 直接配置使用,plugin需要引入再使用
我们先了解loader
的几种书写方式:
// 一个loader
{
test: xxx,
loader: xxx,
options: {...} // 默认配置可以省略 options
}
// 多个loader
{
test: xxx,
use: [
'loader1', // 使用默认配置
{
loader: 'loader2',
options: {...} // 需要修改 loader2 的配置
}
]
}
打包js文件
webpack 是JavaScript的模块打包工具,默认直接可以打包.js
打包json文件
webpack 默认可以打包.json
,无需 loader 处理
打包样式文件
css文件
默认不可以打包 .css
,引入 style.css
文件
npm run build
提示需要对 .css
进行 loader 处理
// webpack.config.js
module.exports = {
...
module: {
rules: [
// 详细 loader 配置
{
// test:匹配文件 .css结尾
test: /\.css$/,
// use:使用哪些loader处理
// use数组中loader的执行顺序:从后往前,这里先经过css-loader处理生成字符串,再通过style-loader处理进行插入生效
use: [
// 使用js的方式创建style标签,将转换后的css文件内容插入标签中并添加到html的head中生效
'style-loader',
// 将css文件变成commonjs模块整合到js文件中,里面内容是样式字符串
'css-loader'
]
}
]
}
}
npm i css-loader style-loader -D
npm run build
浏览器直接打开index.html
,打包成功且样式也生效了
less文件
.less
处理和 .css
处理过程类似,这里就不赘述了,不同的是多了一个 less-loader
转换
// webpack.config.js
{
test: /\.css$/,
...
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
// 将less文件编译成css文件(需要安装less和less-loader)
'less-loader'
]
}
注意:这里每个样式文件都会最终处理生成一个 style
标签,例如 index.js
中引入了 style.css
和 style.less
最终在浏览器中查看元素可以看到生成了两个 style
标签插入在 head
中。
打包html资源
上述我们打包的成果物是一个js
文件,想要使用这个文件需要我们手动创建一个html
文件并将其引入,如果我们打包的时候修改了成果物文件名,那我们就需要手动修改引入的js
文件名,有没有什么办法,可以自动生成一个html
文件,且可以自动引入我们的成果物呢?
npm i html-webpack-plugin -D
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
module: {...},
plugins: [
// 默认会创建一个空的html文件(没有结构),引入打包输出的所有资源
new HtmlWebpackPlugin()
]
}
npm run build
此时,会在我们的成果物中看到生成了build/index.html
内容如下:
如何输出一个有结构的html
文件呢?比如我们已存在的一个index.html
是一个项目文件(有结构和内容的),需要对这个项目的js
打包并自动引入,那我们怎么做呢?
// webpack.config.js
plugins: [
// 默认会创建一个空的html文件(没有结构),引入打包输出的所有资源
new HtmlWebpackPlugin({
// 复制 ./src/index.html 文件,并自动引入打包输出的所有资源
template: './index.html'
})
]
npm run build
页面模板和打包后生成的index.html
内容对比如下(webpack 会自定引入打包资源,所以我们的index.html
模板本身不用引入js
文件,如果引入了,打包后的index.html
文件就会引入两次,反而会出错):
浏览器直接打开build/index.html
,内容生效。
如果你还想通过配置修改模板的其他内容,比如 title
、favicon
或者文件重命名等,可以参考html-webpack-plugin官网。
打包图片文件
css 背景图
最新版的 css-loader
已经可以支持对图片资源的处理,也就是说如果我们采用 css
背景图的形式(background-image: url(./jpg1.jpg)
)引入的图片资源不需要做loader处理就可以打包(只要我们已经对 css
和 less
进行了 loader 处理即可)。
js 导入图
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
...
<img src="#" alt="" id="img1">
</head>
</html>
npm run build
// webpack.config.js
module: {
rules: [
...
{
test: /\.(jpg|png|gif)$/,
// 多个loader用use,单个loader可以用loader
// url-loader依赖file-loader,所以需要安装这两个依赖
loader: 'url-loader',
// url-loader的配置
options: {
// 图片资源小于8kb就会被处理成base64(减少请求数量,但是图片体积会增大)
limit: 8 * 1024
}
}
]
}
npm i url-loader file-loader -D
npm run build
此时还是显示不出来,为什么?
因为url-loader
默认使用的是es6模块化,此时变量 jpg_1
的图片资源被打包成了一个模块,如果我们要给图片的src
添加设置图片地址,需要拿到的是模块的内容才能显示出图片,也就是这样:
// index.js
img_el_1.src = jpg_1.default
这样做,显然不符合我们平时的开发习惯,所以我们还是希望打包解析的时候不要将图片解析成es6 module
,而是commonjs
,那如何改写配置呢?
// webpack.config.js
{
test: /\.(jpg|png|gif)$/,
...
options: {
...
// 关闭es6模块化,使用commonjs解析
esModule: false
}
},
// index.js
img_el_1.src = jpg_1
浏览器直接打开build/index.html
,图片显示,打包成功。
html 文件中引入图
问题:上述的url-loader
中对于图片的处理默认是处理不了html
文件中的img
图片的,打包后生成的html
文件中的img
标签也是没有变化的,我们打包后的build/index.html
和模板index.html
获取图片的相对位置显示是不同的,如果路径没有变化,打包后的图片是显示不出来的,因为路径是不对的,那需要如何处理呢?
// webpack.config.js
module: {
rules: [
...
{
test: /\.html$/,
// 处理html文件的img图片(负责引入html文件中的img,从而能够被url-loader处理)
loader: 'html-withimg-loader'
}
]
}
npm i html-withimg-loader -D
npm run build
浏览器直接打开build/index.html
,打包成功。
上述打包结果可以看到我们的成果物build/
中只有一张图片资源文件,但其实我们通过js导入和img写入两种方式加载了两张一样的图片,这也说明了webpack在打包时,相同的资源只会打包生成一个结果。
webpack-dev-server
编写代码是一个连续的过程,如果每次我们改动的内容都需要手动打包才能看到效果,这种工作方式未免效率太低了,那有没有什么工具可以帮助我们开发阶段的时候不需要打包也能实时的看到修改的效果呢?这就是webpack-dev-server
的作用
npm i webpack-dev-server -D
// package.json
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server" // 本地安装,命令行执行需要:npx webpack-dev-server
},
// webpack.config.js
module.exports = {
...
module: {...},
plugins: [...],
// devServer:用来自动编译,开发环境下使用的配置
// 注意:只会在内存中编译打包,不会有任何输出到本地代码
devServer: {
// 我们要运行的内容是打包后的 build 内容,而不是我们的源代码 src 内容
// dev-server 运行的是内存中的 build 下的内容,所以即使没有打包还是可以运行展现内容
static: path.join(__dirname, 'build'),
// 启动gzip压缩
compress: true,
port: 3000
}
}
npm run dev
浏览器会自动打开http://localhost:3000/,本地生效。此时webpack实现了自动编译,当src/
目录下内容发生了变化后就会自动编译打包并自动刷新浏览器。
- 执行
npm run build(即:npx webpack)
会严格按照配置文件输出打包成果物 - 执行
npm run dev(即:npx webpack-dev-server)
指令只会在内存中编译打包返回给浏览器,不会输出打包内容,一旦终止运行就会删除内存中的打包内容。
文件归整
后续文件越来越多文件结构也会越来越乱,所以我们将相同格式的文件归整到同一文件夹下,最终整理的结果如下(记得修改涉及到的文件引用路径,否则会报错噢):
虽然我们的源代码已经经过了文件夹归类整理,但是我们打包结果还是平级展示,如何将我们的打包结果也按照这种文件夹的形式归类呢?
// webpack.config.js
module.exports = {
...
output: {
...
filename: 'js/bundle.js' // js打包到js文件夹下
},
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
...
options: {
...
// 图片资源打包到imgs文件夹下
outputPath: 'imgs'
}
},
]
}
}
npm run build
注意:上面css资源打包章节中我们提到过,css
内容经过 css
的几个 loader 处理后,最终是通过 js
语句将转化后的 css
字符串内容插入到页面的 head
的 style
标签中的,所以也就不存在打包后的 .css
文件归类了。
版本
文章中涉及到的包版本号
"css-loader": "^6.7.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"html-withimg-loader": "^0.1.16",
"less": "^4.1.2",
"less-loader": "^11.0.0",
"style-loader": "^3.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.73.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.1"
注意:不同的webpack版本配置内容会有所不同
资源
项目代码获取
git clone https://gitee.com/potatocoder/webpack_learn.git