概述
点关注,不迷路!先点赞,后观看,养成阅读好习惯!你长得这么好看,点个赞不过分吧~
首先,webpack是个啥呢?我们来看一下官方的定义:
Webpack is a
static module bundler
formodern
JavaScript applications.
翻译过来就是“Webpack是一个用于现代JavaScript应用程序的静态模块打包器。”
我们进行一下拆解:
打包bundler
:webapck可以帮助我们打包,所以它是一个打包工具。静态的static
:这样表述的原因是因为我们最终可以将代码打包成静态资源(部署到静态服务器)模块化module
:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等现代的modern
:正式因为现代前端开发面临各式各样的问题,才催生了webpack的出现和发展。
我们知道前端开发最常使用的三大框架就是React、Vue和Angular,事实上,这三大框架的创建过程都是借助于脚手架CLI的。而Vue-Cli、create-react-app、Angular-Cli都是基于webpack来帮助我们支持模块化、less、TypeScript、打包优化等。
那webpack与其他常见的打包工具gulp、rollup有什么区别呢?这点改天我们单独开一篇文章好好唠唠。
简单来说就是gulp更加强调前端流程的自动化,模块化不是
它的核心;webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能;而rollup和定义和定位和webpack都非常相似,但是配置相对于webpack来说更加的简洁,使用场景有所不同。通常在实际项目开发过程中,我们都会使用webpack(比如vue、react、angular项目都是基于webpack的);
在对库文件进行打包时,我们通常会使用rollup(比如vue、react、dayjs源码本身都是基于rollup的);
使用步骤
好了,接下来就让我们进入正题吧!
安装node环境
Webpack的运行是依赖 Node 环境 的,所以我们电脑上必须有 Node 环境。
这里给出官方的地址: node官方网址。需要注意的是我们本篇文章都是基于最新版的webpack5.x进行讲述的,所以你下载的node最好是最新的LTS版的。
新建一个my-webpack文件夹
用来存放我们接下来的项目
初始化项目:npm init
npm init // 可以添加一些自己对项目的说明,例如作者姓名、项目描述、使用的协议等
或
npm init -y // 生成一个默认的package.json文件
package.json 文件是对项目或者模块包的描述,里面包含许多元信息。比如项目名称,项目版本,项目执行入口文件,项目贡献者等等。npm install 命令会根据这个文件下载所有依赖模块。
安装webpack、webpack-cli
npm install webpack webpack-cli -g // 全局安装
npm install webpack webpack-cli -D // 局部安装
这里我们采用局部安装,执行命令后会默认生成package-lock.json文件,锁定项目依赖的文件。
我们要同时安装这两个包。那这两个包有什么区别呢?
- 执行webpack 命令,会执行 node_modules 下的 .bin 目录下的 webpack
- webpack在执行时是依赖 webpack-cli 的,如果没有安装就会报错;
- 而webpack-cli 中代码执行时,才是真正利用 webpack 进行编译和打包的过程;
- 所以在安装webpack 时,我们需要同时安装 webpack-cli (第三方的脚手架事实上是没有使用 webpack-cli的,而是类似于自己的 vue-service-cli 的东西)
再总结一下就是:
- webpack:编译和打包都是他来做。
- webpack-cli:帮你形成文件的映射,知道用什么东西打包什么文件,然后交给webpack指令打包。
举个栗子就是。你执行npm run build指令webpack就会对文件进行打包,但webpack指令是不知道用什么打包工具打包各类文件的,webpack-cli做的就是这部分工作,找到正确的工具然后交给webpack指令编译和打包。
准备工作:根目录新建index.html、dist文件夹和src文件夹
dist文件夹可建和不建,还要在src中新建一个main.js文件,文件的说明在下方
配置入口、出口文件:根目录新建webpack.config.js(可改名)
将main.js配置为项目的入口文件,webpack打包会从入口文件开始查找,把入口文件引用的所有文件都进行打包。按照下图所示进行配置
配置打包命令
在package.json里的scripts对象中配置打包命令,按照下图所示进行配置,在这里就可以修改配置文件webpack.config.js的名字了。
接下来在终端输入输入npm run build,就发现打包成功了
处理样式相关文件 – css、sass、less、stylus等
这里以css和less的处理举例,其他都类似。安装相关loader。
npm install css-loader less less-loader style-loader -D
在src文件夹下新建css文件夹,然后新建新建1个css文件和一个less文件div_style.css,title_style.less。
div_style.css
.content {
font-size: 20px;
color: red;
user-select: none;
background-color: #66666666;
}
title_style.less
@fontSize: 50px;
@fontColor: blue;
.title {
font-size: @fontSize;
color: @fontColor;
user-select: none;
}
然后在
然后在wk.config.js(因为上面改名了)文件中进行如下配置
const path = require("path")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
// 所有loader的配置规则都在这里进行配置(也可单独抽取出去,下面会讲到)
module: {
rules: [
{
test: /\.css$/,
use: [ "style-loader", "css-loader"]
},
{
test: /\.less$/,
use: [ "style-loader", "css-loader", "less-loader"]
}
},
}
这里针对上述的配置做如下说明:
- 关于style-loader文件:css-loader只是负责将css文件进行解析,并不会将解析后的css插入到页面之中,这部分工作就是交给style-loader来做的。
- 关于loader的书写顺序:必须按照样例的书写顺序进行书写,因为loader的读取顺序是从右向左的。
在src下面新建component文件下,然后在其下面新建cpns.js文件。创建元素并使用刚才的css、less样式文件。
cpns.js
import "../css/div_style.css"
import "../css/title_style.less"
// 创建div元素
const divEl = document.createElement("div")
divEl.textContent = "Hello Webpack"
divEl.classList.add("content")
document.body.append(divEl)
// 创建h2元素
const titleEl = document.createElement("h2")
titleEl.textContent = "哈哈哈哈"
titleEl.classList.add("title")
document.body.append(titleEl)
在index.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>Learn Webpack</title>
</head>
<body>
<script src="./build/bundle.js"></script>
</body>
</html>
执行npm run build
并打开index.html文件。如图,说明我们的配置起效果了。
处理图片相关文件 – jpg、jpeg、png、svg、gif、webp等
这里有两点需要说明:
- 在webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader。
- 从webpack5开始,我们可以直接使用资源模块类型( asset module type),来替代上面的这些loader。
资源模块类型 (asset module type) ,通过添加 4 种新的模块类型,来替换所有这些 loader:
- asset/resource发送一个单独的文件并导出 URL 。
之前通过使用 file-loader 实现;
- asset/inline导出一个资源的 data URI。
之前通过使用 url -loader实现;
- asset/source导出资源的源代码
之前通过使用raw-loader实现;
- asset 在导出一个 data URI和发送一个单独的文件之间自动选择
之前通过使用 url-loader,并且配置资源体积限制实现。
在src下面新建文件夹img,然后在其中放入两张图片,一张小于100kb,一张大于100kb。在css文件夹下新建bg_style.css ,内容如下
bg_style.css
.img-bg {
width: 500px;
height: 500px;
background: url(../img/LessThan100.jpg);
}
在cpns.js文件中新增内容,如下:
cpns.js
import "../css/div_style.css"
import "../css/title_style.less"
import "../css/bg_style.css"
// 引入图片模块
import MoreThan100 from "../img/MoreThan100.png"
// 创建div元素
const divEl = document.createElement("div")
divEl.textContent = "Hello Webpack"
divEl.classList.add("content")
document.body.append(divEl)
// 创建h2元素
const titleEl = document.createElement("h2")
titleEl.textContent = "哈哈哈哈"
titleEl.classList.add("title")
document.body.append(titleEl)
// 创建img元素
const imgEl = document.createElement("img")
imgEl.src = MoreThan100
document.body.append(imgEl)
// 创建div元素, 设置背景
const divBgEl = document.createElement("div")
divBgEl.classList.add("img-bg")
document.body.append(divBgEl)
在wk.config.js中增加如下配置
const path = require("path")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
module: {
rules: [
// 刚刚已经做过的配置
// ...
{
test: /\.(png|jpe?g|svg|gif|webp)$/,
// 1.打包两张图片, 并且这两张图片有自己的地址, 将地址设置到img/bgi中
// 缺点: 多图片加载的两次网络请求
// type: "asset/resource",
// 2.将图片进行base64的编码, 并且直接编码后的源码放到打包的js文件中
// 缺点: 造成js文件非常大, 下载js文件本身消耗时间非常长
// 造成js代码的下载和解析/执行时间过长
// type: "asset/inline"
// 3.合理的规范:
// 3.1.对于小一点的图片, 可以进行base64编码
// 3.2.对于大一点的图片, 单独的图片打包, 形成url地址, 单独的请求这个url图片
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 100 * 1024 // 超过100kb(自定义)的就单独打包,否则转base64编码
}
},
generator: {
// 占位符
// name: 指向原来的图片名称
// ext: 扩展名
// hash: webpack生成的hash
filename: "img/[name]_[hash:8][ext]"
}
},
]
},
}
删除build文件夹,重新运行npm run build,结果如下。可以看到大于100kb的图片被打包成了一个单独的图片文件,而小于100kb的图片则被转为了base64编码。
配置babel
babel是什么?Babel 是一个工具链 ,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript ;
包括:语法转换、源代码等。
需要安装的包:babel-loader,@babel/preset-env。
对@babel/preset-env的说明:
常见的预设有三个:
- env
- react
- TypeScript
npm install babel-loader @babel/preset-env -D
配置wk.config.js,如下
const path = require("path")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
module: {
rules: [
// 刚刚已经做过的配置
// ...
{
test: /\.m?js$/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
},
]
},
}
重新执行打包命令,可以看到bundle.js中找不到const定义的内容了
将babel-loader的配置单独抽取出去
如果觉得wk.config.js中的内容过多的话,也可以将babel-loader的配置内容抽取到一个单独的文件中去,其他配置文件的抽取类似
在根目录下新建一个**babel.config.js(在你没有能力更改时,名字是固定的,不要瞎编!!!
)**文件,内容如下
babel.config.js
module.exports = {
presets: [
"@babel/preset-env"
]
}
然后wk.config.js中babel的配置只要这样写就行了
{
test: /\.m?js$/,
use: ["babel-loader"]
},
重新打包后可以看到打包后的效果和上面是一致的,bundle.js中找不到“const”之类的内容。
打包.vue文件
需要安装的包:vue-loader、@vue/compiler-sfc(安装vue时就已经安装好了)
npm install vue-loader vue -D
配置wk.config.js,如下
const path = require("path")
const { VueLoaderPlugin } = require("vue-loader/dist/index")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
plugins: [new VueLoaderPlugin()],
module: {
rules: [
// 刚刚已经做过的配置
// ...
{
test: /\.vue$/,
loader: "vue-loader"
}
]
},
}
在src文件下新建Hello.vue文件,内容如下:
<template>
<div>
<!-- html -->
<h2 class="title_vue">{{title}}</h2>
<p class="content_vue">
我是内容, 哈哈哈哈哈哈哈哈
</p>
</div>
</template>
<script>
export default {
data() {
return {
title: "我是Vue的标题"
}
}
}
</script>
<style>
.title_vue {
color: green;
font-size: 100px;
}
.content_vue {
color: yellow;
font-size: 30px;
}
</style>
在index.html
中增加一个Hello.vue的预挂载元素:div#app
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>learn webpack</title>
</head>
<body>
<div id="app"></div>
<script src="./build/bundle.js"></script>
</body>
</html>
然后在main.js中引入Hello.vue,并将其挂载到div#app上去
main.js
import { createApp } from "vue"
import Hello from "./vue_demo/Hello"
// 刚刚的代码
// ...
// Vue代码
createApp(Hello).mount("#app")
重新执行打包命令,可以看到.vue文件被成功解析了。
文件路径的解析和配置:配置别名&自动添加扩展名
我们知道,在项目中,我们经常需要从其他文件中引入文件,有时候呢,我们需要引入的文件比较多,并且这些文件的层级可能还比较深,而且还有不同的扩展名需要书写。这个时候,有些同学可能会说,不是有代码提示吗,也不用我去费力写啊!但是,你想想,如果你引入了许多文件,每个文件都是这样:“…/…/…/…/xxx/xx/xx.xx”写的话真的好吗?所以,这部分工作就交给webpack来做吧。
- extensions: 解析到文件时自动添加扩展名,默认值是[‘.wasm’, '.mjs ', ‘.js’, ‘.json’],可以自己添加其他的。
- alias:为文件路径配置别名。
在wk.config.js中添加如下配置。
const path = require("path")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js"
},
// 刚刚已经做过的配置
// ...
resolve: {
// 这样我们后期引入如下后缀名的文件就不用写后缀名了
extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
// 这样我们下次再引入src下面utils下面的文件时,就不用写成src/utils/了
// 直接写成utils/就行了
alias: {
utils: path.resolve(__dirname, "./src/utils"),
test: "utils/abc/cba/nba/why"
}
},
}
如下图,在utils文件下新建两个文件math.js和test.js,将test.js放到很深的地方去
math.js
export function sum(num1, num2) {
return num1 + num2
}
test.js
import { sum } from "utils/math.js"
console.log(sum(20, 30))
在main.js中引入并使用。
main.js
// 只写这一行代码
// 单独看一下我们的配置的别名和省略扩展名的写法有没有生效
// 前面一个test表示配置的深层文件夹的别名
// 后面一个表示的是深层文件夹下的test.js这个文件
import "test/test"
重新执行打包命令,如图,解析成功
loader与plugin的区别
loader:用于 特定的模块类型
转换。
plugin:用于执行更加广泛的任务
,比如打包优化、资源管理、环境变量注入等。
Webpack默认只能处理js文件,处理其他文件需要安装相应的loader。
webpack插件之clean
前面演示时,每次修改配置重新打包时,都要手动删除build文件夹,有些太麻烦,这件事情我们可以交给一个插件CleanWebpackPlugin来做
npm install clean-webpack-plugin -D
重新执行打包命令后就能看到,打包文件夹build里的内容先被自动删除,然后又重新构建了。
当然,webpack5现在还有一种更简便的方法:直接在output中配置clean
属性即可
wk.config.js
const path = require("path")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js",
clean: true
},
// 之前的代码
// ...
}
webpack插件之打包html
我们看到,我们最终打包的代码中是没有index.html这个文件的,但是进行对应的部署时,是必须要有对应的入口文件index.html的,所以我们也需要对index.html文件进行打包。
打包html需要用到html-webpack-plugin这个插件
npm install html-webpack-plugin -D
然后在wk.config.js文件中增加如下配置:
wk.config.js
const path = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js",
clean: true
},
plugins: [
new HtmlWebpackPlugin({
title: "Learn Webpack",
// 将当前文件下的index.html作为模板
template: "./index.html"
}),
],
// 之前的代码
// ...
}
配置后,我们要将原先的index.html引入bundle.js的部分删除掉,否则一会会发现bundle.js文件被引入了两次。
重新打包后我们发现在build文件夹中生成了一个index.html文件,并且自动引入了打包的bundle.js文件。
那这个文件是如何生成的呢?
- 默认情况下是根据
ejs的一个模板
来生成的。(注 EJS:Embedded JavaScript templating 是一个JavaScript模板库,用来从JSON数据中生成HTML字符串) - 在html-webpack-plugin的源码中,有一个default_index.ejs模块是负责这部分工作的。
自定义HTML模板
有时,我们想要在自己的模块中加入一些特别的内容,比如:
- 添加一个
noscript
标签,在用户的JS被关闭时,给予一些提示。 - 在开发
vue/react
项目中,我们需要添加一个可以挂载后续组件的根标签<div id="app"></div>
这个时候我们就需要一个属于自己的index.html模板
自定义的index.html
<!DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
上面的 代码中,会有一些类似这样语法 <% 变量 %>
,这个是EJS模块填充数据的方式
。
在配置 HtmlWebpackPlugin时,我们可以添加如下配置:
- template:指定我们要使用的模块所在路径;
- title:在进行 htmlWebpackPlugin.options.title读取时,就会读到该信息
我们上面的配置也是按照这种方式配置的。
如果我们用自定义的index.html文件替换掉原先的index.html文件,直接打包的话是会出现问题的,因为有个变量BASE_URL我们并没有进行定义。这个时候就要用到插件DefinePlugin了,它是webpack内置的插件,不需要单独安装。在wk.config.js增加如下配置:
wk.config.js
const path = require("path")
const { DefinePlugin } = require("webpack")
module.exports = {
mode: 'development',
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, './build'),
filename: "bundle.js",
clean: true
},
plugins: [
// 在DefinePlugin定义的变量会变成全局变量
new DefinePlugin({
// 这里写的东西会被当做JS代码去执行,相当于进行了eval操作
// 因此要写成"'./'",这样经过eval("'./'") = './'才是我们想要的结果(字符串)
BASE_URL: "'./'",
name: "'Lee'",
counter: "123"
})
],
// 之前的代码
// ...
}
重新打包后就会看到如下结果了,说明配置成功了:
webpack开启本地服务器
从上面的操作中我们可以看出,每改一次代码就得重新打包一次,非常繁琐,有没有可以改代码自动重新打包的呢?这就要用到
webpack-dev-server了
npm i webpack-dev-server -D
到wk.config.js中配置devServer
devServer: {
hot: true, // 热模块更新,默认为true
// host: "0.0.0.0",
// port: 8888, // 设置监听的端口,默认8080
// open: true // 是否打开浏览器
// compress: true // 是否为静态文件开gzip compression,默认false
},
然后到package.json中配置一下启动命令
"scripts": {
"build": "webpack --config wk.config.js",
"serve": "webpack serve --config wk.config.js"
},
此时我们运行npm run serve就可以启动项目啦!
总结
本篇文章我们详细讲述了webapck5.x的安装,以及常见的loader和plugin的使用,但是针对一些细节性的问题没有进行描述,比如HMR,Proxy,@babel/preset 的预设等等,如果再写的话,这篇文章就太长了。常见的内容讲的差不多了,如果遇到个别不常见的配置内容,大家就去查阅官方的文档和网上的资料吧。
我们现在只是会用了,但针对webpack的优化又该如何下手呢?不要急,好饭不怕晚,在肝了,在肝了~