Webpack系列之构建Webpack知识体系 | 青训营笔记

108 阅读13分钟

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

前言

1. 为什么学习 Webpack?

Webpack 是一种用于构建 JavaScript 应用程序的静态模块打包器,它能够以一种相对一致且开放的处理方式,加载应用中的所有资源文件(图片、CSS、视频、字体文件等),并将其合并打包成浏览器兼容的 Web 资源文件。

image.png

前端项目的构成

在旧时代,我们只能用原生 JavaScript(ES5)、CSS、HTML 方式编写页面代码,开发与生产环境代码基本一致,开发与运行效率都非常低;其次,页面的图片、代码、CSS 等资源都能且只能通过 imgscriptlink 等标签插入到页面中,我们需要非常精细地管理、设计各个标签出现的位置、顺序,这也会占用我们非常多的精力与注意力;并且,开发环境和生产环境一致,难以接入 TS 或 JS 新特性,以及CSS预处理工具。如下图代码片段:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="shortcut icon" type="image/x-icon" href="./images/log.ico" media="screen">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>嗨.音乐</title>
    <link rel="stylesheet" type="text/css" href="./fonts/font-awesome-4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="./plugins/swiper/swiper-bundle.min.css">
    <link rel="stylesheet" type="text/css" href="./css/base.css">
    <link rel="stylesheet" type="text/css" href="./css/index.css">
    <link rel="stylesheet" type="text/css" href="./css/header.css">
    <link rel="stylesheet" type="text/css" href="./css/login.css">
    <link rel="stylesheet" type="text/css" href="./css/main_show.css">
    <script src="./plugins/swiper/swiper-bundle.min.js"></script>
    <script src="./js/flexible.js"></script>
    <script src="./js/jquery.min.js"></script>
    <script src="./js/index.js"></script>
</head>
<body>
    <!--网页logo 介绍 首行-->
    <div class="head">
        <div class="head_nav">
            <a href="index.html">发现音乐</a>
            <a href="index.html">我的音乐</a>
            <a href="sub/shop.html" target="_blank">商城</a>
        </div>
        <!--导航部分-->
        <div class="nav" id="nav">

2. 为什么是Webpack?

image.png

  1. 深入学习 Webpack,不仅能帮助你更快解决具体的工程技术问题,还能形成属于你个人的,极具区分度的核心竞争力!
  2. Vite 一类 Unbundle 工具定位于解决特定问题,而 Webpack 则几乎无所不能,功能覆盖小程序、桌面应用、微前端、WASM 等诸多场景,许多情况下 Webpack 依然是最优解。
  3. 同类工具或多或少都有借鉴 Webpack 之处,虽然具体实现差异很大,但解决工程化问题的思路基本一致,所谓一通百通,深入理解 Webpack 底层逻辑,以及处理具体问题的方式方法后,相同的知识必然也能套用到同类工具中。
  4. Webpack 还在持续迭代发展,V5 之后推出的持久化缓存、lazyCompilation 等特性极大强化了构建性能,未来虽不大可能超越 Unbundle 方案的性能优势,但相信会逐渐缩小差距,直至可被用户接受。

3. Webpack 的优点

  • 所有资源都是 Module,所以可以用同一套代码实现诸多特性,包括:代码压缩、Hot Module Replacement、缓存等;
  • 打包时,资源与资源之间非常容易实现信息互换,例如可以轻易在 HTML 插入 Base64 格式的图片;
  • 借助 Loader,Webpack 几乎可以用任意方式处理任意类型的资源,例如可以用 Less、Stylus、Sass 等预编译 CSS 代码。
  • Webpack 具有极强的开放性,也让它得以成为前端工程化环境的 基座,我们可以围绕 Webpack 轻易接入一系列工程化工具,例如 TypeScript、CoffeScript、Babel 一类的 JavaScript 编译工具;或者 Less、Sass、Stylus、PostCSS 等 CSS 预处理器;或者 Jest、Karma 等测试框架,等等。
  • 自 2012 年首次发布至今,Webpack 还处于快速迭代成长阶段,社区依然保持极大活力;Webpack 已经发布了最新的 5.73.0 版本,经过 5 个大版本迭代以及社区的不断努力,现如今的 Webpack 已经非常非常成熟。

4. 如何学习 Webpack?

img

1. 入门应用

  • 理解打包流程
  • 熟练掌握常用配置项、Loader、插件的使用方法,能够灵活搭建继承 Vue、React、Babel、Eslint、Less、Sass、图片处理等工具的 Webpack 环境。
  • 掌握常见脚手架的用法,例如:Vue-cli,create-react-app、@angular/cli

2. 进阶

  • 理解 Loader、Plugin 机制,能够自行开发 Webpack 组件
  • 理解常见性能优化手段、并能解决实际问题
  • 理解前端工程化概念与生存现状

3. 大师级

  • 阅读源码,理解 Webpack 编译、打包原理,甚至参与共建

1、使用 Webpack

1. 第一个 Webpack项目

首先新建文件夹 wepack-test, 通过编辑器打开,文件结构:

├── src
|   └── index.js
└── webpack.config.js

填写内容: webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js', //项目入口文件
  mode: 'development', //环境
  devtool: false,
  output: { //输出目录
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  }
}

index.js

document.write("hello Webpack!");

进行项目初始化,安装 Webpack:在项目根目录下,打开终端,执行命令:

npm init # 一路回车
npm i -D webpack webpack-cli

image.png

在项目根目录下,打开终端,执行编译命令,构建项目:

npx webpack

执行成功输出生产文件:

image.png

image.png

2. 理解 Webpack配置项

核心流程 -- 极度简化版

Webpack 原生提供了上百种配置项,这些配置最终都会作用于 Webpack 打包过程的不同阶段,因此我们可以从Webpack 编译流程来了解各项配置的作用。

image.png

  1. 入口处理:从 entry 配置项开始,启动编译流程
  2. entry 配置项所指定的入口文件开始,根据 requireimport 等语句找到依赖资源
  3. 根据 moudle 配置项,调用资源转移器,将 png、css 等非标准 JS 资源转译为 JS 内容
  4. 递归调用 1 2,指定所有引用的文件和非标准 JS 资源处理完毕
  5. 将转译后的资源内容合并打包为可直接在浏览器运行的 JS 文件

webpack 特点 模块化 + 一致性

  • 多个文件资源合并成一个,减少 http 请求数
  • 支持模块化开发
  • 支持高级 JS 特性
  • 支持 TypeScript CoffeeScript 方言
  • 统一图片、CSS、字体等其他资源的处理模型
  • ...

那么,怎么使用 Webpack?

Webpack 的使用方法,基本都围绕“配置”展开,而这些配置大致可分为两类:

  • 流程类:作用于流程中某个或若干个环节,直接影响打包效果的配置项
  • 工具类:主流程之外,提供更多工程化能力的配置项

流程类配置项综述

与打包流程强相关的配置项有:

  • 输入输出:

    • entry:用于定义项目入口文件,Webpack 会从这些入口文件开始按图索骥找出所有项目文件;
    • context:项目执行上下文路径;
    • output:配置产物输出路径、名称等;
  • 模块处理:

    • resolve:用于配置模块路径解析规则,可用于帮助 Webpack 更精确、高效地找到指定模块
    • module:用于配置模块加载规则,例如针对什么类型的资源需要使用哪些 Loader 进行处理
    • externals:用于声明外部资源,Webpack 会直接忽略这部分资源,跳过这些资源的解析、打包操作
  • 后处理:

    • optimization:用于控制如何优化产物包体积,内置 Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能
    • target:用于配置编译产物的目标运行环境,支持 web、node、electron 等值,不同值最终产物会有所差异
    • mode:编译模式短语,支持 developmentproduction 等值,可以理解为一种声明环境的短语

这里的重点是,Webpack 首先需要根据输入配置(entry/context) 找到项目入口文件;之后根据按模块处理(module/resolve/externals 等) 所配置的规则逐一处理模块文件,处理过程包括转译、依赖分析等;模块处理完毕后,最后再根据后处理相关配置项(optimization/target 等)合并模块资源、注入运行时依赖、优化产物结构等。

这些配置项与打包流程强相关,建议学习时多关注它们对主流程的影响,例如 entry 决定了项目入口,而 output 则决定产物最终往哪里输出;resolve 决定了怎么找到模块,而 module 决定了如何解读模块内容,等等。

工具类配置项综述

除了核心的打包功能之外,Webpack 还提供了一系列用于提升研发效率的工具,大体上可划分为:

  • 开发效率类:

    • watch:用于配置持续监听文件变化,持续构建
    • devtool:用于配置产物 Sourcemap 生成规则
    • devServer:用于配置与 HMR 强相关的开发服务器功能
  • 性能优化类:

    • cache:Webpack 5 之后,该项用于控制如何缓存编译过程信息与编译结果
    • performance:用于配置当产物大小超过阈值时,如何通知开发者
  • 日志类:

    • stats:用于精确地控制编译过程的日志内容,在做比较细致的性能调试时非常有用
    • infrastructureLogging:用于控制日志输出方式,例如可以通过该配置将日志输出到磁盘文件

逻辑上,每一个工具类配置都在主流程之外提供额外的工程化能力,例如 devtool 用于配置产物 Sourcemap 生成规则,与 Sourcemap 强相关;devServer 用于配置与 HMR 相关的开发服务器功能;watch 用于实现持续监听、构建。

工具类配置内聚性较强,通常一个配置项专注于解决一类工程问题,学习时建议先对配置项按其功能做个简单分类,例如上述开发效率类、性能优化类等,之后再展开研究其可选值与效果。

img

3. 通过例子了解 Webpack 配置过程

引入CSS预处理器,处理CSS资源

首先搭建项目,示例文件结构:

.
├── src
|   ├── index.js
|   └── index.css
└── webpack.config.js

填写内容

index.js

import styles from './index.css' 

index.css

.box{
    width: 100px;
    height: 100px;
}

初始化项目,安装 Webpack

npm init # 一路回车
npm i -D webpack webpack-cli

安装 CSS 处理器 loader

npm add -D css-loader style-loader

配置 webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: false,
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    //CSS处理器
    rules: [{
      test: /.css/i,
      use: [
        'style-loader',
        'css-loader'
      ]
    }]
  }
}

在项目根目录下,打开终端,执行编译命令,构建项目:

npx wabpack

image.png

问题

  1. Loader 有什么作用?为什么这里需要用到 css-loader、style-loader?
  2. 与旧时代 ---- 在 HTML 文件中维护 css 相比,这种方式会有什么优劣处?
  3. 有没有接触过 Less、Sass、Stylus 这一类 CSS 预编译框架?如何在 Webpack 接入这些工具?

回答

Loader有什么作用?

Webpack Loader 最核心的只能是实现内容转换器 —— 将各式各样的资源转化为标准 JavaScript 内容格式,例如:

  • css-loader 将 css 转换为 __WEBPACK_DEFAULT_EXPORT__ = ".a{ xxx }" 格式
  • html-loader 将 html 转换为 __WEBPACK_DEFAULT_EXPORT__ = "<!DOCTYPE xxx" 格式
  • vue-loader 更复杂一些,会将 .vue 文件转化为多个 JavaScript 函数,分别对应 template、js、css、custom block

为什么这里需要用到 css-loader、style-loader ?

  • 本质上是因为 Webpack 只认识符合 JavaScript 规范的文本(Webpack 5之后增加了其它 parser):在构建(make)阶段,解析模块内容时会调用 acorn 将文本转换为 AST 对象,进而分析代码结构,分析模块依赖;这一套逻辑对图片、json、Vue SFC等场景就不工作了,就需要 Loader 介入将资源转化成 Webpack 可以理解的内容形态。

与旧时代 ---- 在 HTML 文件中维护 css 相比,这种方式会有什么优劣处?

  • 优点:统一管理
  • 缺点:耦合度高

有没有接触过 Less、Sass、Stylus 这一类 CSS 预编译框架?如何在 Webpack 接入这些工具?

  • 有,接入 less 示例,在理解loader章节叙述

引入 Babel 处理 JS 兼容性问题

图解

Babel 的作用: 用于将 JS 语法降级, 处理兼容性, 如将 ES6 规范,转换为 ES5 规范

为什么这样做:

  • JavaScript 语法标准繁多, 浏览器支持程度不一
  • 开发者需要用到高级语法

安装 babel 依赖

npm i -D @babel/core @babel/preset-env babel-loader

配置 webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: false,
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [{ //less处理
      test: /.less$/i,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    },{ //js处理 babel
      test: /.js?$/,
      use:[{
          loader: 'babel-loader',
          options:{
            presets:[
              ['@babel/preset-env']
            ]
          }
        }]
    }]
  }
}

执行 npx webpack

image.png

问题

  1. Babel 具体有什么功能?
  2. Babel 与 Webpack 分别解决了什么问题? 为何能协作到一起?

回答

Babel 具体有什么功能 ?

  • Babel 插件大致分为两种:语法插件和转换插件。语法插件作用于 @babel/parser,负责将代码解析为抽象语法树(AST)(官方的语法插件以 babel-plugin-syntax 开头);转换插件作用于 @babel/core,负责转换 AST 的形态。

Babel 与 Webpack 分别解决了什么问题? 为何能协作到一起?

  • Babel 解决了项目的兼容性问题
  • Webpack 使项目模块化, 解决依赖问题
  • Webpack 主要是将非标准 JS 资源转译为 JS 内容,进行统一资源管理,而 JS 可能在不同浏览器上存在兼容问题,所以需要 Babel 来进行协作

引入HTMLWebPackPlugin 生成 HTML

安装 HTMLWebPackPlugin 插件

npm i -D html-webpack-plugin

配置 webpack.config.js

const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: false,
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [{ //less处理
      test: /.less$/i,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    },{ //js处理
      test: /.js?$/,
      use:[{
          loader: 'babel-loader',
          options:{
            presets:[
              ['@babel/preset-env']
            ]
          }
        }]
    }]
  },
  //插件
  plugins:[new HtmlWebpackPlugin()]
}

执行 npx webpack

问题

  1. 相比于手工维护 HTML 内容,这种自动生成的方式有什么优缺点?

回答

相比于手工维护 HTML 内容,这种自动生成的方式有什么优缺点?

  • 优点:简化了HTML文件的创建,无需手工维护 HTML 文件
  • 缺点:配置繁琐

开启模块热替换工具,进行项目的实时预览

HMR Hot Moudle Replacement --- 模块热替换工具:热更新代码,当你修改代码保存后,会自动更新,实时显示

安装 devserver

npm install -D webpack-dev-server

配置 webpack.config.js

const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: false,
  devServer:{
    hot: true, //开启热加载
    open:true
  },
  watch:true, //持续监听代码改变生成新版本
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [{ //less处理
      test: /.less$/i,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    },{ //js处理
      test: /.js?$/,
      use:[{
          loader: 'babel-loader',
          options:{
            presets:[
              ['@babel/preset-env']
            ]
          }
        }]
    }]
  },
  //插件
  plugins:[new HtmlWebpackPlugin()]
}

执行命令,运行项目至浏览器

npx webpack serve

使用 Tree-Shaking 删除没有使用的代码

形象解释-摇一摇树,清除落叶

Tree-Shaking 树摇 工具

把定义的代码,但没有进行使用,进行删掉

使用 Tree-Shaking, 配置 webpack.config.js

const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'production', //使用树摇,注意把 模式改为 生产环境
  devtool: false,
  devServer:{
    hot: true, //开启热加载
    open:true
  },
  optimization:{
    usedExports:true //开启 树摇
  },
  watch:true, //持续监听代码改变生成新版本
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [{ //less处理
      test: /.less$/i,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    },{ //js处理
      test: /.js?$/,
      use:[{
          loader: 'babel-loader',
          options:{
            presets:[
              ['@babel/preset-env']
            ]
          }
        }]
    }]
  },
  //插件
  plugins:[new HtmlWebpackPlugin()]
}

验证: 编写 index.js bar.js

//bar.js
let bar1 = '1';
let bar2 = '2';
​
export {
    bar1,
    bar2
}
​
//index.js
import {
    bar,
    bar2
} from './bar'console.log(bar);

注意此时是生产环境,执行 npx webpack

以下效果是开发环境和生产环境的对比

其他工具

  • 缓存
  • Sourcemap
  • 性能监控
  • 日志
  • 代码压缩
  • 分包

3、进阶篇:理解 Loader

1. 概述

问题:Webpack 只认识JS

为了处理非标准 JS 资源,设计出资源翻译模块---Loader,用于将资源翻译为标准JS

2. 使用Loader

文件结构

├── src
|   ├── a.less
|   ├── b.less
|   └── index.js
└── webpack.config.js

引入 less

 npm install less less-loader -D 

配置 webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devtool: false,
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist')
  },
  module: {
    //less处理器
    rules: [{
      test: /.less$/i,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    }]
  }
}

index.js

import styles from './a.less'

认识Loader -- 链式调用

使用上,可以为某种资源文件配置多个 Loader,Loader 之间按照配置的顺序从前到后(pitch),再从后到前依次执行,从而形成一套内容转译工作流,例如对于下面的配置:

module.exports = {
  module: {
    rules: [
      {
        test: /.less$/i,
        use: [
          "style-loader",
          "css-loader",
          "less-loader",
        ],
      },
    ],
  },
};

loader 执行流程

上述示例中,三个 Loader 分别起如下作用:

  • less-loader:实现 less => css 的转换,输出 css 内容,无法被直接应用在 Webpack 体系下
  • css-loader:将 css 内容包装成类似 module.exports = "${css}" 的内容,包装后的内容符合 JavaScript 语法
  • style-loader:做的事情非常简单,就是将 css 模块包进 require 语句,并在运行时调用 injectStyle 等函数将内容注入到页面的 style 标签

认识Loader :其它特性

  • 链式执行
  • 支持异步执行
  • 分 normal ,pitch 两种模式

参考:Webpack 原理系列七:如何编写loader

2. 编写loader

image.png image.png

3. 常见 Loader

站在使用角度,建议掌握这些常见的 Loader 的功能、配置方法

image.png

4、进阶篇:理解插件

1. 概述

插件是什么?

  • 插件是为了给程序提供一种扩展能力
  • 比如 Vs Code、Vue

甚至、Webpack 本身的很多功能都是插件来实现的,我们可以通过下图看出:

Webpack 编译流程

2. 使用插件

  1. 安装插件依赖 npm
  2. 在 webpack 配置,引入插件
  3. 创建插件实例

image.png

3. 写webpack插件

首先:插件围绕 “钩子“ 展开

image.png

钩子的核心信息:

  1. 时机 :编译过程的特点阶段,Webpack 会以钩子形式通知插件此刻正在发生什么事情
  2. 上下文:通过 tapable 提供的回调机制,以参数形式传递上下文信息
  3. 交互:在上下文参数对象中附带很多存在 side effect 的交互接口,插件可以通过这些接口改变

image.png

时机: compier.hooks.compliation

参数:compliation

交互:dependencyFactories.set

image.png

5、总结

我们前面学了:

  • Webpack 的作用
  • 理解 Webpack 配置结构,学习关键配置项
  • Loader 的作用与常用 Loader
  • 插件基本形态与作用

推荐学习网址:

Webpack 官方文档

Babel 官网

Webpack 原理系列七:如何编写loader

Webpack 原理系列十:HMR 原理全解析

深入浅出 Webpack · 深入浅出

Webpack 5 知识体系 - GitMind