什么是 Webpack

  1. 官网

  2. 官方解释:webpack is a static module bundler for modern JavaScript applications.

  3. 翻译一下:webpack 是一个静态的模块化打包工具,为现代的 JavaScript 应用程序;

  4. 我们来对上面的解释进行拆解:

    • 打包 bundler:webpack 可以将帮助我们进行打包,所以它是一个打包工具;
    • 静态的 static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
    • 模块化 module:webpack 默认支持各种模块化开发,ES Module、CommonJS、AMD 等;
    • 现代的 modern:现代前端的开发越来越复杂,催生了 webpack 的出现和发展;
  5. 即:无论开发使用什么语言,最终通过 webpack 将代码打包成普通的静态资源(详见下文:Webpack 依赖图);

环境

Webpack 的运行是依赖 Node 环境的,所以我们电脑上必须有 Node 环境;

安装

webpack 的安装目前分为两个:webpack(代码使用)、webpack-cli(命令行使用);

这里推荐局部安装:假如电脑上有 3 个项目:vue2,vue3,react;三个项目都需要打包,可能依赖的 webpack 版本不一样;

所以:应该每个项目有着自己的 webpack,需要用什么版本,在本地的局部环境安装对应的 webpack 版本即可;

plaintext
1
2
npm install webpack webpack-cli –g # 全局安装(不推荐)
npm install webpack webpack-cli –D # 局部安装(-D开发环境安装,生产环境已经打包成静态资源了,不需要使用webpack)

基础打包

默认打包

  1. 创建个文件夹,使用 npm init -y 创建 package.json;
  2. npm i webpack webpack-cli -D 安装局部环境依赖;
  3. 在根目录下新建 src 文件夹,文件夹下新建入口文件 index.js,写入任意 js 代码;
  4. 在根目录下 cmd:通过 npx webpack 进行打包 ==> 打包成功:根目录下生成 dist 文件夹,下面有 main.js;(可以将此打包之后的 js 引入 html 中,可以正常运行);
  5. 但是如果根目录下没有 src 文件夹 (其他名字的文件夹也不行),或者 src 文件夹下的 js 不叫 index.js 进行打包 ==> 打包失败;(即:文件夹必须叫 src,入口文件必须叫 index.js);
  6. 那如果就不想有 src 文件夹或 index.js 如何进行打包呢?这里用到了配置参数;

配置参数

修改文件夹名为 location,下面 js 改为 main.js;

  • 指定入口;
plaintext
1
npx webpack --entry ./location/main.js   #-entry为入口文件的意思, 后面跟 入口文件的路径;

如何修改打包之后的文件夹名和下面的 js 名呢?

  • 指定输出文件夹名、输出文件名;
plaintext
1
2
3
npx webpack --entry ./location/main.js --output-filename fsl.js --output-path fsllala
#--output-filename fsl.js 指定输出文件名为 fsl.js
#--output-path fsllala 指定输出文件夹名为 fsllala

但是这样写配置参数,太长了,不方便,如何解决呢?==> 单独创建配置文件;

配置文件

在根目录下新建 webpack.config.js(固定名字),里面进行参数配置;

此时目录结构如下:

plaintext
1
2
3
4
5
6
7
8
|—— node_modules
|—— package.json
|—— package-lock.json
|—— webpack.config.js #配置文件
└── location #源码目录 (一般叫src,这里是为了改配置,所以改为了location)
|── main.js #入口文件 (一般叫index.js,这里是为了改配置,所以改为了main.js)
|── name.js #依赖文件
└── age.js #依赖文件
  • webpack.config.js 其实是个模块,webpack 的运行环境为 nodejsnodejs 会将该模块导出的内容进行读取;所以需要使用 node 的 CommonJs;不能使用 ES6 模块,即:
js
1
2
// webpack.config.js文件
module.exports={} // commonJs
  • 进行相关参数配置
js
1
2
3
4
5
6
7
8
const path = require("path");
module.exports = {
entry: "./location/main.js", // 入口文件
output: {
filename: "fsl.js", // 输出文件名
path: path.resolve(__dirname, "fsllala"), // 输出文件路径 (这里必须是绝对路径)
},
};
  • 如果不想叫 webpack.config.js,而是叫 vue.config.js,运行 npx webpack==> 报错;
plaintext
1
npx webpack --config vue.config.js # 通过指定配置文件名,可以解决
  • npm run build 是怎么做到打包的呢
js
1
2
3
4
// package.json
"scripts": {
"build":"webpack --config vue.config.js" // 这里不是cmd,所以去掉npx
}

进阶打包

Webpack 依赖图

webpack

webpack 到底是如何对我们的项目进行打包的呢?

  • 事实上 webpack 在处理应用程序时,它会根据命令或者配置文件找到入口文件;
  • 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js 文件、css 文件、图片、字 体等);
  • 然后遍历图结构,打包一个个模块 (webpack 默认只给 JS 文件打包,别的文件需要根据文件的不同使用不同的 loader 来解析);

loader

新建目录结构如下:

plaintext
1
2
3
4
5
6
7
8
9
|—— node_modules
|—— package.json
|—— package-lock.json
|—— webpack.config.js #配置文件
|—— index.html
└── src #源码目录
|── index.js #入口文件
|── components # 存放js的文件夹
|── css # 存放css的文件夹

验证 loader 作用

验证上问提及:webpack 默认只给 JS 文件打包,别的文件需要根据文件的不同使用不同的 loader 来解析;

期望效果:

  1. index.html 文件中仅引入打包之后的入口文件,别的不做任何操作;
  2. 页面展示有 css 样式的内容;

编写验证代码

  1. css 文件夹下新建 div_style.css
css
1
2
3
4
5
/* css/div_style.css */
.redColor{
color: red;
font-size: 24px;
}
  1. components 文件夹下新建 div_cpns.js
js
1
2
3
4
5
6
// components/div_cpns.js
import "../css/div_style.css"; // 模块引入;添加到依赖图中 (css模块没有进行导出)
const div = document.createElement("div");
div.innerHTML = "hello world";
div.classList.add("redColor");
document.body.append(div);
  1. 入口文件引入 div_cpns.js
js
1
2
// src/index.js
import "./components/div_cpns.js"; // 模块引入;添加到依赖图中 (div_cpns.js模块没有进行导出)
  1. 使用 npm run build 进行打包
plaintext
1
2
3
4
# package.json 
"scripts": {
"build":"webpack"
},

打包出错,打包结果如下图所示:

css-loader1

上面的错误信息告诉我们需要一个 loader 来加载这个 css 文件,但是 loader 是什么呢?

  • loader 可以用于对模块的源代码进行转换;
  • 我们可以将 css 文件也看成是一个模块,我们是通过 import 来加载这个模块的;
  • 在加载这个模块时,webpack 其实并不知道如何对其进行加载,我们必须制定对应的 loader 来完成这个功能;
  • webpack 默认支持对 js 文件的处理;其他文件需要根据文件的不同使用不同的 loader 来解析;

css-loader 与 style-loader

  • css-loader:可以解析 css 文件的 loader。但该 loader 并不会将解析之后的 css 插入到页面中,即如果只使用该 loader 会产生 css 解析完了,但是页面没有效果的现象;
  • style-loader:会将解析之后的 css 插入到页面,即完成插入 style 的操作;

loader 下载:

plaintext
1
npm install css-loader style-loader -D  #loader其实都是研发环境的,生产环境都是解析之后的静态文件;

loader 配置

  • webpack.config.js 文件中的 module.rules 中允许我们配置多个 loader;
  • module.rules 的配置如下:
    • rules 属性对应的值是一个数组,数组中存放的是一个个的 Rule,Rule 是一个对象,对象中可以设置多个属性:
      • test 属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;
      • use 属性:对应的值是一个数组,数组里为各种 loader;注意:loader 的执行顺序是从后往前的;
      • use 属性数组内可以为对象,对象属性为:
        • loader:必须有一个 loader 属性,对应的值是一个字符串;
        • options:可选的属性,值是一个字符串或者对象,值会被传入到 loader 中
        • query:目前已经使用 options 来替代;
      • use 属性数组内可以为字符串,是 loader 属性的简写方式;
js
1
2
3
4
5
6
7
8
9
10
11
12
module.exports={
module:{
rules:[
{
// 告诉webpack匹配什么文件(.在正则中表示除换行符之外的任何字符,所以需要\进行转义)
test:/\.css$/,
// 告诉webpack使用什么loader进行匹配之后的处理(loader的执行顺序是从后往前的)
use:['style-loader','css-loader']
}
]
}
}

使用 npm run build 进行打包 ==> 打包成功;

index.html 引入打包之后的文件,查看效果:

html
1
<script src="./dist/main.js"></script>

less-loader

less 同理,环境不支持该预处理器,需将现将 less、sass 等编写的 css 需要通过工具转换成普通的 css;

loader 下载

plaintext
1
npm install less-loader -D

配置 webpack.config.js

js
1
2
3
4
5
6
7
8
9
10
module.exports={
module:{
rules:[
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader'] // less会转化为css,所以还需要css-loader
}
]
}
}

PostCSS

参考文献:PostCssPostCSS 真的太好用了!

loader 下载

plaintext
1
npm install postcss-loader -D

autoprefixer

  • 有一些 css 属性存在浏览器兼容问题,需要开发手动添加浏览器前缀;
  • 而 Autoprefixer 将使用基于当前浏览器支持的特性和属性数据去为你自动添加前缀 (-webkit-,-ms-,-moz-)。你可以尝试下 Autoprefixer 的 demo:Autoprefixer CSS online

使用 postcss 插件 Autoprefixer:

  1. 下载
plaintext
1
npm install autoprefixer -D
  1. 设置单独的 postcss 配置文件:根目录下,新建 postcss.config.js(注意:因为 postcss 需要有对应的插件才会起效果,所以我们需要配置它的 plugin);
js
1
2
3
4
// postcss.config.js
module.exports = {
plugins: ["autoprefixer"],
};
  1. 设置 loader
js
1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
module.exports={
module:{
rules:[
{
test:/\.css$/,
// postcss先对css进行处理,所以写在后面(从后往前解析)
use:['style-loader','css-loader','postcss-loader']
},
]
}
}
  1. 相关 css 代码
css
1
2
3
4
5
6
/* css/div_style.css */
.redColor{
color: red;
font-size: 24px;
user-select: none; /* 这个属性有兼容差异,这里我们没做处理, Autoprefixer自动做浏览器兼容处理*/
}
  1. npm run build 进行打包,index.html 引入打包之后的文件,检查 css
  2. 效果如下

Autoprefixer

postcss-preset-env

  • 集成了 postcss 常用的插件 (Autoprefixer,polyfill……);
  • 所以用 postcss-preset-env,就不需要安装使用 Autoprefixer 了,因为内部已经集成了 Autoprefixer;

使用 postcss 插件 postcss-preset-env 同 Autoprefixer 步骤;

Webpack 打包图片

asset module type

  • 我们当前使用的 webpack 版本是 webpack5:

    • 在 webpack5 之前,加载这些资源我们需要使用一些 loader,比如 raw-loader 、url-loader、file-loader;(需要下载对应的 loader,然后使用)
    • 在 webpack5 开始,我们可以直接使用资源模块类型(asset module type),来替代上面的这些 loader;(不需要下载 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,并且配置资源体积限制实现)

asset module type 的使用

  1. 新建文件夹 img,用于存放图片;
  2. 在 components 下,新建 img_cpns.js;
js
1
2
3
4
5
6
7
8
9
// src/components/img_cpns.js

// 在webpack中,任何文件都是一个模块,都可以引入其模块;
// 引入图片模块
import kunkun from "../img/ikun.jpg"; // 因为要对该图片使用,所以起了个别名,而不是直接引入

const img = document.createElement("img");
img.src = kunkun;
document.body.append(img);
  1. 在入口文件里面引入 img_cpns.js,构成 webpack 的依赖图结构;
js
1
2
3
4
// src/index.js
import "./components/div_cpns.js"; // 模块引入;添加到依赖图中 (div_cpns.js模块没有进行导出)

import "./components/img_cpns.js"; // 模块引入;添加到依赖图中 (img_cpns.js模块没有进行导出)
  1. webpack5 版本开始,不需要下载资源 loader,只需要配置 asset module type 即可;

将图片全部转换为一个单独的文件

  • 配置 webpack
js
1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
module.exports={
module:{
rules:[
{
test:/\.(png|jpe?g|gif|svg|webp)$/, // 图片资源 (jpe?g表示jpg和jpeg格式)
// 打包图片,将图片打包成一个单独的文件,然后引入
type:'asset/resource',
}
]
}
}
  • 通过 npm run build 进行打包;观察打包之后的 dist 文件夹;**(每次打包前需要删除之前的 dist 文件夹,防止之前的分包依旧保留,后续可以通过配置实现自动删除)**

发现将原来的 ikun.jpg,打包成了 1d40d06e317136088c5a.jpg;然后 main.js 对打包之后的图片地址做了处理;

assetResource

如何做到自定义输出文件名呢?

自定义输出文件名:官网

  • 配置 webpack
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|webp)$/, // 图片资源 (jpe?g表示jpg和jpeg格式)
// 打包图片,将图片打包成一个单独的文件,然后引入
type: "asset/resource",
// 匹配符合test条件的文件(如果直接写到output,就不会匹配test条件)
generator: {
filename: "[name]_[hash][ext]",
// [name]代表原文件名;
// [etx]代表原文件后缀;
// [hash]唯一ID(防止打包前不同文件夹内有名字后缀都相同的文件,打包之后无法区分)
},
},
],
},
};

通过 npm run build 进行打包;观察打包之后的 dist 文件夹;**(每次打包前需要删除之前的 dist 文件夹,防止之前的分包依旧保留,后续可以通过配置实现自动删除)**

发现将原来的 ikun.jpg,打包成了:ikun_1d40d06e317136088c5a.jpg;

如何将图片资源放到一个单独的文件夹内呢?

  • 配置 webpack
js
1
2
3
generator: {
filename: "images/[name]_[hash][ext]",
},

将图片全部转换为 base64 格式;

  • 优点:相比较于 asset/resource,少发送 (图片加载) 网络请求;

  • 缺点:造成 JS 文件偏大,下载 JS 文件、解析本身消耗时间非常长 ==> 造成代码阻塞;

  • 配置 webpack

js
1
2
3
4
5
6
7
8
9
10
11
module.exports={
module:{
rules:[
{
test:/\.(png|jpe?g|gif|svg|webp)$/, // 图片资源 (jpe?g表示jpg和jpeg格式)
// 打包图片,inline的寓意就是将图片转为base64,然后放到了打包之后的main.js里面(行内)
type:'asset/inline',
}
]
}
}
  • 通过 npm run build 进行打包;观察打包之后的 dist 文件夹;**(每次打包前需要删除之前的 dist 文件夹,防止之前的分包依旧保留,后续可以通过配置实现自动删除)**

发现 dist 文件夹里面只有一个 main.js;

assetInline

  • 浏览器运行,可看到图片正常显示,图片地址为 base64:data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD…P2atuIjKPvHtU5hxbHHVqzLS6xGJv4G6CtISEIPRulecZn//Z

可通过合理的配置,决定图片的转化;

合理的配置:

  • 小一点的图片:进行 base64 编码;

  • 大一点的图片:单独的图片打包,形成 url 地址,单独的请求这个 url 图片;

  • 配置 webpack:官网

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|webp)$/, // 图片资源 (jpe?g表示jpg和jpeg格式)
// 打包图片
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 18 * 1024, // 18kb (小于18kb,转为base64;大于18kb,单独打包成文件)
},
},
},
],
},
};

Babel

  • webpack 可以打包 JS 文件,但是 ES6 + 的语法不会转化为 ES5;
  • Babel 是一个工具链,主要用于旧浏览器或者环境中将 ECMAScript 2015 + 代码转换为向后兼容版本的 JavaScript;
  • 包括:ES6+,TypeScript、Vue、React 等通过 Babel 转换为向后兼容版本的 JavaScript;
  • 在线 Babel 转换:官网

插件

Babel 的使用,需要安装相应的插件 (这里不过多介绍,一般来说安装预设 preset 即可);

plaintext
1
2
3
# 基本不安装此插件
npm install @babel/plugin-transform-arrow-functions -D #箭头函数 转成 普通函数 插件
npm install @babel/plugin-transform-block-scoping -D # const/let 转成 var 插件

Babel 的预设 preset

集成了 Babel 常用的插件;

  • 安装
plaintext
1
2
npm install @babel/preset-env -D  #安装预设preset
npm install babel-loader -D #安装babel-loader
  • 使用
js
1
2
3
4
5
6
7
8
9
10
11
12
//webpack.config.js
module.exports = {
module: {
rules: [
{
test:/\.js$/,
use:["babel-loader"],
}
],
},
};

js
1
2
3
4
// 新建babel.config.js
module.exports={
presets:["@babel/preset-env"],
}
  • 入口文件
js
1
2
3
4
5
6
7
// // src/index.js
// import "./components/div_cpns.js"; // 模块引入;添加到依赖图中 (div_cpns.js模块没有进行导出)

// import "./components/img_cpns.js"; // 模块引入;添加到依赖图中 (img_cpns.js模块没有进行导出)

const message="hello babel";
message.split(" ").map((item)=>console.log(item))
  • 打包 npm run build,查看打包之后的 main.js;(箭头函数转化为了普通函数)
js
1
"hello babel".split(" ").map((function(l){return console.log(l)}));

Plugin

  • Loader 是用于特定的模块类型进行转换;(文件解析、转换)
  • Plugin 可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;(文件解析、转换之外的所有事情);
  • 官网

CleanWebpackPlugin

每次修改了一些配置,重新打包时,都需要手动删除 dist 文件夹,CleanWebpackPlugin 可以自动化删除 (需要配置输出目录)。

Tip:5.20.0 + 的版本,可以直接配置 output==> 官网

  • 安装
plaintext
1
npm install clean-webpack-plugin -D
  • 配置
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
// 必须要指定输出目录位置,否则不会自动清除;
output: {
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"], // postcss先对css进行处理,所以写在后面(从后往前解析)
},
],
},
// 使用插件(必须要指定输出目录位置,否则不会自动清除)
plugins: [new CleanWebpackPlugin()],
};
  • 测试

在之前打包的 dist 文件夹中,随便新建个文件,比如 abc.js==> 打包之后被清除;