构建 Webpack 知识体系 | 青训营

154 阅读6分钟

构建 Webpack 知识体系

为什么要学习Webpack

  • 理解前端“工程化”概念、工具、目标
  • 一个团队总要有那么几个人熟悉 Webpack,某种程度上可以成为个人的核心竞争力
  • 高阶前端必经之路

课程内容和目标

主要内容:

  • 什么是 Webpack

  • Webpack 打包核心流程

    • 示例
    • Entry => Dependencies Lookup => Transform => Bundle => Output
    • 关键配置项介绍
  • Loader 组件

  • Plugin 组件

  • 如何学习 Webpack

    • 入门级:学会灵活应用
    • 进阶:学会扩展 Webpack
    • 大师:源码级理解 Webpack 打包编译原理

课程目标:

  • [√] 理解 Webpack 的基本用法
  • [√] 通过介绍 Webpack 的功能、Loader 与 Plugin 组件设计,建立一个知识体系
  • [×] 不会事无巨细,介绍 Webpack 所有
  • [×] 不是深入源码,讲解底层是实现原理

尽量不会涉及复杂繁琐的概念,便于理解。

1 什么是Webpack

  1. Webpack 的本质

    1. Webpack 定义解析
    2. Webpack 优势

前端项目由什么构成?——资源

image.png

当然,可以手动管理这些资源:

<!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>Title</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <div>
        <img src="./img.jpg" alt="">
    </div>
    <script src="./main.js"></script>
    <script src="./foo.js"></script>
    <script src="./bar.js"></script>
</body>
</html>

但,

  • 依赖手工,比如由多个 JS 文件...操作,过程繁琐
  • 当代码文件之间有依赖的时候,就得严格按依赖顺序书写
  • 开发与生产环境一致,难以接入 TS 或 JS 新特性
  • 比较难接入 Less、Sass 等工具
  • JS、图片、CSS 资源管理模型不一致

手动过程非常耗时耗人,提高了管理和项目成本,对规范的要求多而严格,难以构建比较大的项目,效率等问题都会受到比较大的影响。这些都是旧时代非常突出的问题,对开发效率影响非常大,直到。。。

09 年左右至今,出现了很多工程化的工具,包括解决JS文件模块化问题的构建工具、任务运行器的设计:

某种程度上正是这些工具的出现,才有了“前端工程”这一概念

  • Gulp
  • REQUIRE.JS
  • browserify
  • Grunt

后来出现了比较新一代的、使用非常普遍的构建工具:

  • Webpack
  • Vite
  • rollup.js

Webpack刚出来时其实是不被看好的,后来发现 Webpack 能解决更多问题,自动化程度相对来说更高,行业里使用 Webpack 的人越来越多,后来 Webpack 于是就慢变成了使用率特别广的一个工具了。

Webpack本质上是一种前端资源编译、打包工具image.png

  • 多份资源文件打包成一个 Bundle
  • 支持 Bable、Eslint、TS、CoffeScript、Less、Sass
  • 支持模块化处理 CSS、图片 等资源文件
  • 支持 HMR + 开发服务器
  • 支持持续监听、持续构建
  • 支持代码分离
  • 支持 Tree-shaking
  • 支持 Sourcemap
  • ...

2 Webpack的使用

简单写一个例子:

image.png

Webpack 安装

webpack安装.png

Webpack 配置

webpack配置demo.png

执行编译命令

执行webpack编译命令.png

打包产物如下例(不完全):

webpack产物.png

Webpack 核心流程 —— 极度简化版

image.png

Webpack 做的事情 —— 模块化、一致性

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

使用 Webpack

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

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

举个栗子

webpack使用 - 使用webpack.png

流程类配置

image.png

输入:

  • entry: 定义项目的入口,Webpack 从这个入口开始处理整个项目
  • context: Webpack 在要运行的时候从哪个文件夹去找资源

模块解析:

  • resolve
  • externals

模块转译:

  • module:很复杂的一个配置项属性

后处理:跟最终产物处理有关的

  • optimization
  • mode
  • target

配置总览

image.png

webpack英文官网

webpack 中文文档|webpack 中文网

使用 Webpack 实例

文件结构

image.png

  1. 声明入口entry

入口声明.png

  1. 声明产物出口output

出口声明.png

  1. 运行npx webpack,结果应该如下(不完全):

image.png

还是以前面的Demo(index.js和bar.js)作为例子,把mode更改为默认值"production",打包的产物会做一个压缩,最终产物简洁很多,配置如下:

mode和devtool.png

结果如下:

mode=production.png

下面是几个实际应用实例

1.使用Webpack——接入Babel

babel作用:把高版本的代码转换成低版本的代码,已达到兼容目的。

文件结构:

arduino
复制代码
.
|——src
|   ﹂index.js
|——webpack.config.js

1.安装依赖

bash
复制代码
npm i -D @babel/core @babel/preset-env babel-loader

2.声明产物出口output

css
复制代码
const paht =require("path");
module.exports={
    entry:"./src/index",
    output:{
        filename:"[name].js",
        path.join(__dirname,"./dist"),
    },
    moduel:{
        rules:[{
        test::/.js?$/,
        use:[{
            loader:'babel-loader',
            options:{
            presets:[
                ['@babel/preset-env']
               ]
            }
          }]
       }]
   }
}

3.执行npx webpack

2.使用Webpack——生成HTML

文件结构:

arduino
复制代码
.
|——src
|   ﹂index.js
|——webpack.config.js

1.安装依赖

css
复制代码
npm i -D html-webpack-pligin

2.声明产物出口output

const path = require('path') 
const HTMLWebpackPlugin = require('html-webpack-plugin') 
module.exports = { 
    entry: './src/index', 
    output: { 
          filename: '[name].js', 
        path: path.join(__dirname, './dist') 
    }, 
    plugins: [new HTMLWebpackPlugin()]
}

3.执行npx webpack

运行结果:

​ 文件结构:

.
|——dist
|   ﹂index.html
|   ﹂main.js
|——src
|   ﹂index.js
|——webpack.config.js

image.png

3.使用Webpack——HMR (模块热替换)

可以使我们写的代码不用刷新,就能立刻把最新结果更新到浏览器中

1.开启HMR

const path = require('path') 
const HTMLWebpackPlugin = require('html-webpack-plugin') 
module.exports = { 
    entry: './src/index', 
    mode: 'development', 
    devtool: false, 
    watch: true  //结果立刻呈现
    devServer: {
        hot: true, // 核心配置项 
        open: true 
    }, 
    output: {
        filename: '[name].js', 
        path: path.join(__dirname, './dist')
    },
    module: { 
        rules: [{ test: /.css$/, use: ['style-loader', 'css-loader'] 
        }] 
    }, 
    plugins: [new HTMLWebpackPlugin()], 
}

2.启动Webpack

npx webpack serve

参考资料:HMR 原理全解析mp.weixin.qq.com/s/cbYMpuc4h…

4.使用Webpack——Tree-Shaking

Tree-Shaking作用:删掉没有用到的代码,对工具类库如Lodash有奇效。

1.开启tree-shaking:

const path = require('path') 
module.exports = { 
    entry: './src/index', 
    devtool: false, 
    output: { 
        filename: '[name].js', 
        path: path.join(__dirname, './dist') 
    },
    mode: 'production', //必要 
    optimization: { 
        usedExports: true, //必要
    } 
}

2.执行npx webpack

5.Webpack 工具线

image.png

其他工具:

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

3 理解Loader

作用:

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

认识:下面这3个loader 主要干了些什么

链式调用的方式加载——前一个loader的输出作为后一个loader的输入

image.png

  • less-loader: 实现了 less ~> css 的转换
  • css-loader: 将 CSS 包装成类似 module.exports = "${css}" 的内容,包装后的内容符合 JavaScript 语法
  • style-loader: 将 CSS 模块包进 require 语句,并在运行时调用 injectStyle 等函数将内容注入到页面的 style 标签

特性:

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

image.png

参考资料:如何编写loadermp.weixin.qq.com/s/TPWcB4MfV…

实现eslint-loader:

import getOptions from './ get0ptions ' ;
import Linter from './Linter' ;
import cacheLoader from './cacheLoader ';
export default function loader( content, map){
    const options = getOptions ( this );
    //创建lint实例
    const linter = new Linter( this, options ) ;
    this.cacheable( );
    //return early if cached
    if(options.cache){
        cacheLoader( linter, content,map );
        return;
    }
    //检查输入代码,并返回结果content
    linter.printOutput(linter.lint( content ));
    this.callback( null, content,map );
}

常见Loader

image.png

4 理解插件

什么是插件?为什么需要这么设计?

很多的知名工具都是所谓“插件”架构的 eg:

  • VScode、Webstorm、Chrome、Firefox
  • Babel、Webpack、Rollup、Eslint
  • Vue、Redux、Quill、Axios

如果没有使用“插件”架构,对于一个项目而言是不好的,缺点如下:

  • 新人需要了解整个流程细节,上手成本高
  • 功能迭代成本高,牵一发动全身
  • 功能僵化,作为开源项目而言缺乏成长性
  • Blaba

总结:

  • 心智成本高
  • 可维护性低
  • 缺少生命力

插件架构精髓:是对扩展开放,对修改封闭的一种思维

使用插件

使用html-webpack-plugin

module.exports={
    ...
    plugins:[
        new HTMLWebpackPlugin()
    ]
    ...
}

使用html-webpack-plugin + DefinePlugin

const webpack = require('webpack')
const HtmlwebpackPlugin = require( 'html-webpack-plugin ')
module.exports = {
    entry: "./ src/index" ,
    output: {
        filename: " [name].js ",
        path: path.join( __dirname, " ./dist" )
    },
    plugins:[
        new HtmlwebpackPlugin( ),new webpack. DefinePlugin({
            PRODUCTION:JSON.stringify( true ),
            VERSION: JSON.stringify( ' 5fa3b9'),
        })
    ]
};

如何写插件

写一个插件最需要关注以三个参数:

  • 时机compier.hooks.compilation——钩子在何时触发
  • 参数compilation
  • 交互dependencyFactories.set——在钩子回调中如何和webpack其他上下文内容进行交互

插件围绕钩子展开

image.png

钩子的核心信息:

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

image.png

参考资料:

Webpack 插件架构深度讲解

一文吃透 Webpack 核心原理

5 学习方法

image.png

知识点总结:

image.png