Webpack知识体系 | 青训营笔记

71 阅读4分钟

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

Webpack知识体系

什么是Weboack

image.png

为了解决前端开发时遇到的一系列问题,如依赖手工,难以接入其他Less,Sass工具等,出现了很多工程化工具。

image.png

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

image.png

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

image.png

使用Webpack

示例

1.安装依赖

npm i -D webpack webpack-cli

2.编辑配置文件

在webpack.config.js中配置文件

webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件, 从而基于给定的配置,对项目进行打包。 也就是说,我们所有打包操作都依赖于这个配置文件。

module.exports={
    //声明入口:要打包的文件
    entry:'index.js',
    //声明产物出口
    output:{
        filename:"[name].js",
        //项目打包后的存放位置
        path:path.join(__diename,"./dist"),
    },
    module:{
        rules:[{
            test:/.less$/i,
            use:['style-loader','css-loader','less-loader']
        }]
    }
}

我们观察上面的例子,我们先不考虑里面一些文件的内容,可以把整个export出去的东西看成一个对象。这里面有三个属性分别是,entry,outputmodule,我们先一一来解释一下这三个属性。

  • entry这个属性填的值是整个配置文件的入口文件,也就是我们首先从哪个文件开始打包编译。

  • output这个属性值是最后我们打包压缩完的文件名,也就是说我们填main.js最后就会打包成一个叫main.js的文件

  • module这个属性决定了如何处理项目中的不同类型的模块,其中最重要的个属性是rules,这个属性用来存放我们设置的处理规则。比如:在例子中,test意义是让我们过滤出.less后缀的文件以use中的loader来处理这些文件。

3.执行编译命令

npx webpack

打包后的文件

image.png

webpack打包的流程

这个过程是webpack的打包的核心流程。 大概是这样:首先会取得entry的文件来处理,然后扫描里面的import和require了哪些模块和代码,对应去寻找所在的文件,并用module模块定义的规则来处理找寻到的文件,如果处理过程在又发现import或者require的文件,则接着递归处理下去,直到完全处理完打包生成output的文件。

image.png

webpack实际上主要做了两件事情——模块化 + 一致性

  1. 多个文件资源合并成一个,减少http请求数

  2. 支持模块化开发(importexport对不同类型资源的管理)

  3. 支持高级js特性

  4. 支持Typescript、CoffeeScript方言

  5. 统一图片、css、字体等其他资源的处理模型

  6. Etc···

webpack属性的区分

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

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

流程类配置 image.png

配置总览

按使用频率

  • 'entry/output'

  • 'module/plugins'

  • 'mode'

  • 'watch/devServer/devtool'

image.png

Webpack 配置官方文档

处理CSS

首先我们需要安装loader npm add -D css-loader style-loader 在这里我们只需要重点关注module中的操作即可。

module: {
    rules: [
      { test: /.css$/, use: ['style-loader', 'css-loader'] },
    ]
}

其中,test 表示匹配的文件类型,用正则表达式过滤出为所以后缀为.css的文件。 use 表示对应要调用的 loader,要注意的是

  • use 数组中指定的 loader 顺序是固定的
  • 多个 loader 的调用顺序是:从后往前调用

思考题

1.Loader有什么作用?为什么这里需要用到css-loader、style-loader?

答:loader这里是用来转译过滤出来的文件的,在转译过程是需要调用对应的加载资源库, css-loader、style-loader就是在转译时所需要的库。

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

答:显然在此时我们已经不需要再手动去维护HTML文件中的css代码了,这些多出来的冗余操作可以丢掉了。

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

处理JS

接入Babel webpack 只能打包处理一部分高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借助于 babel-loader 进行打包处理。例如 webpack 无法处理下面的 JavaScript 代码 :

 // 1.定义了名为info的装饰器
function info(target) {

// 2.为目标添加静态属性info
  target.info = 'Person info
}

/1 3.为Person 类应用info 装饰器
@info 
class Person {}

 // 4.打印Person 的静态属性info
console. log(Person. info)
复制代码

安装bable

  • npm i babel-loader @babel/core @babel/preset-env -D
module: {
    rules: [
          // 使用 babel-loader 处理高级的 JS 语法
      // 在配置 babel-loader 的时候,程序员只需要把自己的代码进行转换即可;一定要排除 node_modules 目录中的 JS 文件,因为第三方包中的 JS 兼容性,不需要程序员关心
      { test: /.js$/, use: 'babel-loader', options: { presets: [ '@babel/preset-env' ] } }
    ]
  },

思考题

1.Babel具体有什么功能?

答:Babel 是一个 JavaScript 编译器,我们很多类型的代码文件单独拎出来其实是不能跑的。比如react中的JSX,react也是在脚手架中直接引入babel编译器,把JSX代码解析成JS代码。所以Babel就是把一些其他类型的代码编译成JS。

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

答:Babel是来解析代码变成JS代码,webpack是用来打包压缩整合代码的工具。在webpack中有个东西叫loader,loader需要解析到很多代码文件,两者有同样的应用场景,因此用到出现babel-loader其实并不奇怪。

参考资料

生成 HTML

webpack 中的 HTML 插件(类似于一个模板引擎插件), 可以通过此插件自定制 index.html 页面的内容 。html-webpack-plugin 是 webpack 中的 HTML 插件,可以通过此插件自定制 index.html 页面的内容。 例如:通过 html-webpack-plugin 插件,将 src 目录下的 index.html 首页,复制到项目根目录中一份!

安装包 npm install html-webpack-plugin -D

module.exports = {
    // 插件的数组,将来 webpack 在运行时,会加载并调用这些插件
  plugins: [htmlPlugin, new CleanWebpackPlugin()],
}

plugins这个属性是用来挂载插件的。

image.png

通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中。 HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件

Question:

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

答:在传统项目中,每次对项目打包后我们都需要手动在index.html添加js的链接,当打包后的js入口文件名更改时,我们又需要再一次修改index.html中的js。 但是用自动生成的方式即可避免这种问题。

工具线

工具类一般有这些,如下图:

image.png

1.HMR(模块热替换)

HMR 全称 Hot Module Replacement,中文语境通常翻译为模块热更新,它能够在保持页面状态的情况下动态替换资源模块,提供丝滑顺畅的 Web 页面开发体验。 在这里插入图片描述 使用HRM Webpack 生态下,只需要经过简单的配置即可启动 HMR 功能,大致上分两步:

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    // 必须设置 devServer.hot = true,启动 HMR 功能
    hot: true
  }
};

之后,还需要调用 module.hot.accept 接口,声明如何将模块安全地替换为最新代码,如:

import component from "./component";
let demoComponent = component();

document.body.appendChild(demoComponent);
// HMR interface
if (module.hot) {
  // Capture hot update
  module.hot.accept("./component", () => {
    const nextComponent = component();

    // Replace old content with the hot loaded one
    document.body.replaceChild(nextComponent, demoComponent);

    demoComponent = nextComponent;
  });
}

模块代码的替换逻辑可能非常复杂,幸运的是我们通常不太需要对此过多关注,因为业界许多 Webpack Loader 已经提供了针对不同资源的 HMR 功能,例如:

  • style-loader 内置 Css 模块热更

  • vue-loader 内置 Vue 模块热更

  • react-hot-reload 内置 React 模块热更接口

因此,站在使用的角度,只需要针对不同资源配置对应支持 HMR 的 Loader 即可,很容易上手。

参考资料:

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

2.Tree-Shaking

Tree-shaking(树摇)

用来删除Dead Code(没有被用到的代码)使用Tree-shaking

开启tree-shaking :

  • 'mode: "production" '

  • 'optimization.useExports: true'

PS:对工具类库如Lodash有奇效

image.png

这里解释一下mode结点内容: mode属性:mode 节点的可选值有两个,分别是: developmentproduction.

  • development为开发环境 不会对打包生成的文件进行代码压缩和性能优化 ,打包速度快,适合在开发阶段使用。

  • production为 生产环境, 会对打包生成的文件进行代码压缩和性能优化, 打包速度很慢,仅适合在项目发布阶段使用 。

其他工具:

  • 缓存

  • Sourcemap

  • 性能监控

  • 日志

  • 代码压缩

  • 分包

  • ......

理解 loader

作用:

  • 为了处理非标准JS资源,设计出资源翻译模块

  • 用于将非js资源翻译为标准的js资源

image.png

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

image.png

认识:下面这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

参考资料:如何编写loader

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

实现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;
    }
    //检查输入代码,并返回结果cn见tent
    linter.printOutput(linter.lint( content ));
    this.callback( null, content,map );
}

常见Loader

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

image.png

理解插件

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

很多的知名工具都设计了所谓“插件”架构的 如:

  • 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 核心原理

小结

  • Webpack 的作用

  • 理解 Webpack 配置结构,学习关键配置项

  • Loader 的作用和常用Loader

  • 插件基本形态与作用

Webpack 5 知识体系

学习方法

image.png

知识点

image.png