Webpack 基本使用 | 青训营笔记

68 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的第2天。

基本使用

Webpack 是一个静态资源打包工具。

它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。

输出的文件就是编译好的文件,就可以在浏览器段运行了。

我们将 Webpack 输出的文件叫做 bundle

功能介绍

Webpack 本身功能是有限的:

  • 开发模式:仅能编译 JS 中的 ES Module 语法
  • 生产模式:能编译 JS 中的 ES Module 语法,还能压缩 JS 代码

开始使用

1. 资源目录

 webpack_code # 项目根目录(所有指令必须在这个目录运行)
     └── src # 项目源码目录
         ├── js # js文件目录
         │   ├── count.js
         │   └── sum.js
         └── main.js # 项目主文件

2. 创建文件

  • count.js
 export default function count(x, y) {
   return x - y;
 }
  • sum.js
 export default function sum(...args) {
   return args.reduce((p, c) => p + c, 0);
 }
  • main.js
 import count from "./js/count";
 import sum from "./js/sum";
 ​
 console.log(count(2, 1));
 console.log(sum(1, 2, 3, 4));

3. 下载依赖

打开终端,来到项目根目录。运行以下指令:

  • 初始化package.json
 npm init -y

此时会生成一个基础的 package.json 文件。

需要注意的是 package.jsonname 字段不能叫做 webpack, 否则下一步会报错

  • 下载依赖
 npm i webpack webpack-cli -D

4. 启用 Webpack

  • 开发模式
 npx webpack ./src/main.js --mode=development
  • 生产模式
 npx webpack ./src/main.js --mode=production

npx webpack: 是用来运行本地安装 Webpack 包的。

./src/main.js: 指定 Webpackmain.js 文件开始打包,不但会打包 main.js,还会将其依赖也一起打包进来。

--mode=xxx:指定模式(环境)。

5. 观察输出文件

默认 Webpack 会将文件打包输出到 dist 目录下,我们查看 dist 目录下文件情况就好了

小结

Webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错。

所以我们学习 Webpack,就是主要学习如何处理其他资源。

基本配置

在开始使用 Webpack 之前,我们需要对 Webpack 的配置有一定的认识。

流程分析

image.png

5 大核心概念

  1. entry(入口)

指示 Webpack 从哪个文件开始打包

  1. output(输出)

指示 Webpack 打包完的文件输出到哪里去,如何命名等

  1. loader(加载器)

webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析

  1. plugins(插件)

扩展 Webpack 的功能

  1. mode(模式)

主要由两种模式:

  • 开发模式:development
  • 生产模式:production

准备 Webpack 配置文件

在项目根目录下新建文件:webpack.config.js

 module.exports = {
   // 入口
   entry: "",
   // 输出
   output: {},
   // 加载器
   module: {
     rules: [],
   },
   // 插件
   plugins: [],
   // 模式
   mode: "",
 };

Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范

修改配置文件

  1. 配置文件
 // Node.js的核心模块,专门用来处理文件路径
 const path = require("path");
 ​
 module.exports = {
   // 入口
   // 相对路径和绝对路径都行
   entry: "./src/main.js",
   // 输出
   output: {
     // path: 文件输出目录,必须是绝对路径
     // path.resolve()方法返回一个绝对路径
     // __dirname 当前文件的文件夹绝对路径
     path: path.resolve(__dirname, "dist"),
     // filename: 输出文件名
     filename: "main.js",
   },
   // 加载器
   module: {
     rules: [],
   },
   // 插件
   plugins: [],
   // 模式
   mode: "development", // 开发模式
 };
  1. 运行指令
 npx webpack

此时功能和之前一样,也不能处理样式资源。

小结

Webpack 将来都通过 webpack.config.js 文件进行配置,来增强 Webpack 的功能。

处理样式资源

学习使用 Webpack 如何处理 Css、Less、Sass、Scss、Styl 样式资源

介绍

Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源

我们找 Loader 都应该去官方文档中找到对应的 Loader

Webpack 官方 Loader 文档

处理 Css 资源

1. 下载包

 npm i css-loader style-loader -D

注意:需要下载两个 loader

2. 功能介绍

  • css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容

此时样式就会以 Style 标签的形式在页面上生效

3. 配置

 const path = require("path");
 ​
 module.exports = {
   entry: "./src/main.js",
   output: {
     path: path.resolve(__dirname, "dist"),
     filename: "main.js",
   },
   module: {
     rules: [
       {
         // 用来匹配 .css 结尾的文件
         test: /.css$/,
         // use 数组里面 Loader 执行顺序是从右到左
         use: ["style-loader", "css-loader"],
       },
     ],
   },
   plugins: [],
   mode: "development",
 };

4. 添加 Css 资源

  • src/css/index.css
 .box1 {
   width: 100px;
   height: 100px;
   background-color: pink;
 }
  • src/main.js
 import count from "./js/count";
 import sum from "./js/sum";
 // 引入 Css 资源,Webpack才会对其打包
 import "./css/index.css";
 ​
 console.log(count(2, 1));
 console.log(sum(1, 2, 3, 4));
  • public/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>webpack5</title>
   </head>
   <body>
     <h1>Hello Webpack5</h1>
     <!-- 准备一个使用样式的 DOM 容器 -->
     <div class="box1"></div>
     <!-- 引入打包后的js文件,才能看到效果 -->
     <script src="../dist/main.js"></script>
   </body>
 </html>

5. 运行指令

 npx webpack

打开 index.html 页面查看效果

处理 Less 资源

1. 下载包

 npm i less-loader -D

2. 功能介绍

  • less-loader:负责将 Less 文件编译成 Css 文件

3. 配置

 const path = require("path");
 ​
 module.exports = {
   entry: "./src/main.js",
   output: {
     path: path.resolve(__dirname, "dist"),
     filename: "main.js",
   },
   module: {
     rules: [
       {
         // 用来匹配 .css 结尾的文件
         test: /.css$/,
         // use 数组里面 Loader 执行顺序是从右到左
         use: ["style-loader", "css-loader"],
       },
       {
         test: /.less$/,
         use: ["style-loader", "css-loader", "less-loader"],
       },
     ],
   },
   plugins: [],
   mode: "development",
 };

4. 添加 Less 资源

  • src/less/index.less
 .box2 {
   width: 100px;
   height: 100px;
   background-color: deeppink;
 }
  • src/main.js
 import count from "./js/count";
 import sum from "./js/sum";
 // 引入资源,Webpack才会对其打包
 import "./css/index.css";
 import "./less/index.less";
 ​
 console.log(count(2, 1));
 console.log(sum(1, 2, 3, 4));
  • public/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>webpack5</title>
   </head>
   <body>
     <h1>Hello Webpack5</h1>
     <div class="box1"></div>
     <div class="box2"></div>
     <script src="../dist/main.js"></script>
   </body>
 </html>

5. 运行指令

 npx webpack

打开 index.html 页面查看效果

处理 Sass 和 Scss 资源

1. 下载包

 npm i sass-loader sass -D

注意:需要下载两个

2. 功能介绍

  • sass-loader:负责将 Sass 文件编译成 css 文件
  • sasssass-loader 依赖 sass 进行编译

3. 配置

 const path = require("path");
 ​
 module.exports = {
   entry: "./src/main.js",
   output: {
     path: path.resolve(__dirname, "dist"),
     filename: "main.js",
   },
   module: {
     rules: [
       {
         // 用来匹配 .css 结尾的文件
         test: /.css$/,
         // use 数组里面 Loader 执行顺序是从右到左
         use: ["style-loader", "css-loader"],
       },
       {
         test: /.less$/,
         use: ["style-loader", "css-loader", "less-loader"],
       },
       {
         test: /.s[ac]ss$/,
         use: ["style-loader", "css-loader", "sass-loader"],
       },
     ],
   },
   plugins: [],
   mode: "development",
 };

4. 添加 Sass 资源

  • src/sass/index.sass
 /* 可以省略大括号和分号 */
 .box3
   width: 100px
   height: 100px
   background-color: hotpink
  • src/sass/index.scss
 .box4 {
   width: 100px;
   height: 100px;
   background-color: lightpink;
 }
  • src/main.js
 import count from "./js/count";
 import sum from "./js/sum";
 // 引入资源,Webpack才会对其打包
 import "./css/index.css";
 import "./less/index.less";
 import "./sass/index.sass";
 import "./sass/index.scss";
 ​
 console.log(count(2, 1));
 console.log(sum(1, 2, 3, 4));
  • public/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>webpack5</title>
   </head>
   <body>
     <h1>Hello Webpack5</h1>
     <div class="box1"></div>
     <div class="box2"></div>
     <div class="box3"></div>
     <div class="box4"></div>
     <script src="../dist/main.js"></script>
   </body>
 </html>

5. 运行指令

 npx webpack

打开 index.html 页面查看效果

处理 Html 资源

1. 下载包

 npm i html-webpack-plugin -D

2. 配置

  • webpack.config.js
 const path = require("path");
 const ESLintWebpackPlugin = require("eslint-webpack-plugin");
 const HtmlWebpackPlugin = require("html-webpack-plugin");
 ​
 module.exports = {
   entry: "./src/main.js",
   output: {
     path: path.resolve(__dirname, "dist"),
     filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
     clean: true, // 自动将上次打包目录资源清空
   },
   module: {
     rules: [
       {
         // 用来匹配 .css 结尾的文件
         test: /.css$/,
         // use 数组里面 Loader 执行顺序是从右到左
         use: ["style-loader", "css-loader"],
       },
       {
         test: /.less$/,
         use: ["style-loader", "css-loader", "less-loader"],
       },
       {
         test: /.s[ac]ss$/,
         use: ["style-loader", "css-loader", "sass-loader"],
       },
       {
         test: /.styl$/,
         use: ["style-loader", "css-loader", "stylus-loader"],
       },
       {
         test: /.(png|jpe?g|gif|webp)$/,
         type: "asset",
         parser: {
           dataUrlCondition: {
             maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
           },
         },
         generator: {
           // 将图片文件输出到 static/imgs 目录中
           // 将图片文件命名 [hash:8][ext][query]
           // [hash:8]: hash值取8位
           // [ext]: 使用之前的文件扩展名
           // [query]: 添加之前的query参数
           filename: "static/imgs/[hash:8][ext][query]",
         },
       },
       {
         test: /.(ttf|woff2?)$/,
         type: "asset/resource",
         generator: {
           filename: "static/media/[hash:8][ext][query]",
         },
       },
       {
         test: /.js$/,
         exclude: /node_modules/, // 排除node_modules代码不编译
         loader: "babel-loader",
       },
     ],
   },
   plugins: [
     new ESLintWebpackPlugin({
       // 指定检查文件的根目录
       context: path.resolve(__dirname, "src"),
     }),
     new HtmlWebpackPlugin({
       // 以 public/index.html 为模板创建文件
       // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
       template: path.resolve(__dirname, "public/index.html"),
     }),
   ],
   mode: "development",
 };

3. 修改 index.html

去掉引入的 js 文件,因为 HtmlWebpackPlugin 会自动引入

 <!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>webpack5</title>
   </head>
   <body>
     <h1>Hello Webpack5</h1>
     <div class="box1"></div>
     <div class="box2"></div>
     <div class="box3"></div>
     <div class="box4"></div>
     <div class="box5"></div>
     <i class="iconfont icon-arrow-down"></i>
     <i class="iconfont icon-ashbin"></i>
     <i class="iconfont icon-browse"></i>
   </body>
 </html>

4. 运行指令

 npx webpack

此时 dist 目录就会输出一个 index.html 文件。

Loader 原理 (尝试babel-loader)

loader 概念

帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。

loader 执行顺序

  1. 分类
  • pre: 前置 loader
  • normal: 普通 loader
  • inline: 内联 loader
  • post: 后置 loader
  1. 执行顺序
  • 4 类 loader 的执行优级为:pre > normal > inline > post
  • 相同优先级的 loader 执行顺序为:从右到左,从下到上

例如:

 // 此时loader执行顺序:loader3 - loader2 - loader1
 module: {
   rules: [
     {
       test: /.js$/,
       loader: "loader1",
     },
     {
       test: /.js$/,
       loader: "loader2",
     },
     {
       test: /.js$/,
       loader: "loader3",
     },
   ],
 },
 // 此时loader执行顺序:loader1 - loader2 - loader3
 module: {
   rules: [
     {
       enforce: "pre",
       test: /.js$/,
       loader: "loader1",
     },
     {
       // 没有enforce就是normal
       test: /.js$/,
       loader: "loader2",
     },
     {
       enforce: "post",
       test: /.js$/,
       loader: "loader3",
     },
   ],
 },
  1. 使用 loader 的方式
  • 配置方式:在 webpack.config.js 文件中指定 loader。(pre、normal、post loader)
  • 内联方式:在每个 import 语句中显式指定 loader。(inline loader)
  1. inline loader

用法:import Styles from 'style-loader!css-loader?modules!./styles.css';

含义:

  • 使用 css-loaderstyle-loader 处理 styles.css 文件
  • 通过 ! 将资源中的 loader 分开

inline loader 可以通过添加不同前缀,跳过其他类型 loader。

  • ! 跳过 normal loader。

import Styles from '!style-loader!css-loader?modules!./styles.css';

  • -! 跳过 pre 和 normal loader。

import Styles from '-!style-loader!css-loader?modules!./styles.css';

  • !! 跳过 pre、 normal 和 post loader。

import Styles from '!!style-loader!css-loader?modules!./styles.css';

开发一个 loader

1. 最简单的 loader

 // loaders/loader1.js
 module.exports = function loader1(content) {
   console.log("hello loader");
   return content;
 };

它接受要处理的源码作为参数,输出转换后的 js 代码。

2. loader 接受的参数

  • content 源文件的内容
  • map SourceMap 数据
  • meta 数据,可以是任何内容

手写 babel-loader

作用:编译 js 代码,将 ES6+语法编译成 ES5-语法。

  • 下载依赖
 npm i @babel/core @babel/preset-env -D
  • loaders/babel-loader/index.js
 const schema = require("./schema.json");
 const babel = require("@babel/core");
 ​
 module.exports = function (content) {
   const options = this.getOptions(schema);
   // 使用异步loader
   const callback = this.async();
   // 使用babel对js代码进行编译
   babel.transform(content, options, function (err, result) {
     callback(err, result.code);
   });
 };
  • loaders/banner-loader/schema.json
 {
   "type": "object",
   "properties": {
     "presets": {
       "type": "array"
     }
   },
   "additionalProperties": true
 }

Plugin 原理

Plugin 的作用

通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。

Plugin 工作原理

webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 ——「深入浅出 Webpack」

站在代码逻辑的角度就是:webpack 在编译代码过程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。

Webpack 内部的钩子

什么是钩子

钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack 把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。

Tapable

Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义,它是 webpack 的核心功能库。webpack 中目前有十种 hooks,在 Tapable 源码中可以看到,他们是:

 // https://github.com/webpack/tapable/blob/master/lib/index.js
 exports.SyncHook = require("./SyncHook");
 exports.SyncBailHook = require("./SyncBailHook");
 exports.SyncWaterfallHook = require("./SyncWaterfallHook");
 exports.SyncLoopHook = require("./SyncLoopHook");
 exports.AsyncParallelHook = require("./AsyncParallelHook");
 exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
 exports.AsyncSeriesHook = require("./AsyncSeriesHook");
 exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
 exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
 exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
 exports.HookMap = require("./HookMap");
 exports.MultiHook = require("./MultiHook");

Tapable 还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:

  • tap:可以注册同步钩子和异步钩子。
  • tapAsync:回调方式注册异步钩子。
  • tapPromise:Promise 方式注册异步钩子。

Plugin 构建对象

Compiler

compiler 对象中保存着完整的 Webpack 环境配置,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。

这个对象会在首次启动 Webpack 时创建,我们可以通过 compiler 对象上访问到 Webapck 的主环境配置,比如 loader 、 plugin 等等配置信息。

它有以下主要属性:

  • compiler.options 可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。
  • compiler.inputFileSystemcompiler.outputFileSystem 可以进行文件操作,相当于 Nodejs 中 fs。
  • compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。

compiler hooks 文档

Compilation

compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。

一个 compilation 对象会对构建依赖图中所有模块,进行编译。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

它有以下主要属性:

  • compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块。
  • compilation.chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。
  • compilation.assets 可以访问本次打包生成所有文件的结果。
  • compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。

compilation hooks 文档

总结

本文主要是从 webpack 的基本使用、基本配置、处理样式、处理HTML、Loader、Plugin几个方面让我们对这一个打包工具有一个初步的认识。具体的操作还是需要在真实的开发当中自己去发现。

知识点:

image.png