webpack基础

news2025/1/16 4:02:17

1. 当面试官问Webpack的时候他想知道什么

前言

在前端工程化日趋复杂的今天,模块打包工具在我们的开发中起到了越来越重要的作用,其中webpack就是最热门的打包工具之一。

说到webpack,可能很多小伙伴会觉得既熟悉又陌生,熟悉是因为几乎在每一个项目中我们都会用上它,又因为webpack复杂的配置和五花八门的功能感到陌生。尤其当我们使用诸如umi.js之类的应用框架还帮我们把webpack配置再封装一层的时候,webpack的本质似乎离我们更加遥远和深不可测了。

当面试官问你是否了解webpack的时候,或许你可以说出一串耳熟能详的webpack loaderplugin的名字,甚至还能说出插件和一系列配置做按需加载和打包优化,那你是否了解他的运行机制以及实现原理呢,那我们今天就一起探索webpack的能力边界,尝试了解webpack的一些实现流程和原理,拒做API工程师。

CgqCHl6pSFmAC5UzAAEwx63IBwE024.png

你知道webpack的作用是什么吗?

从官网上的描述我们其实不难理解,webpack的作用其实有以下几点:

  • 模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。
  • 编译兼容。在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpackLoader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。
  • 能力扩展。通过webpackPlugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。

说一下模块打包运行原理?

如果面试官问你Webpack是如何把这些模块合并到一起,并且保证其正常工作的,你是否了解呢?

首先我们应该简单了解一下webpack的整个打包流程:

1、读取webpack的配置参数;

2、启动webpack,创建Compiler对象并开始解析项目;

3、从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;

4、对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;

5、整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。

其中文件的解析与构建是一个比较复杂的过程,在webpack源码中主要依赖于compilercompilation两个核心对象实现。

compiler对象是一个全局单例,他负责把控整个webpack打包的构建流程。 compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程。

而每个模块间的依赖关系,则依赖于AST语法树。每个模块文件在通过Loader解析完成之后,会通过acorn库生成模块代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。

最终Webpack打包出来的bundle文件是一个IIFE的执行函数。

// webpack 5 打包的bundle文件内容

(() => { // webpackBootstrap
    var __webpack_modules__ = ({
        'file-A-path': ((modules) => { // ... })
        'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { // ... })
    })
    
    // The module cache
    var __webpack_module_cache__ = {};
    
    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
                return cachedModule.exports;
        }
        // Create a new module (and put it into the cache)
        var module = __webpack_module_cache__[moduleId] = {
                // no module.id needed
                // no module.loaded needed
                exports: {}
        };

        // Execute the module function
        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

        // Return the exports of the module
        return module.exports;
    }
    
    // startup
    // Load entry module and return exports
    // This entry module can't be inlined because the eval devtool is used.
    var __webpack_exports__ = __webpack_require__("./src/index.js");
})

webpack4相比,webpack5打包出来的bundle做了相当的精简。在上面的打包demo中,整个立即执行函数里边只有三个变量和一个函数方法,__webpack_modules__存放了编译后的各个文件模块的JS内容,__webpack_module_cache__ 用来做模块缓存,__webpack_require__Webpack内部实现的一套依赖引入函数。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。

其中值得一提的是__webpack_require__模块引入函数,我们在模块化开发的时候,通常会使用ES Module或者CommonJS规范导出/引入依赖模块,webpack打包编译的时候,会统一替换成自己的__webpack_require__来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。

你知道sourceMap是什么吗?

提到sourceMap,很多小伙伴可能会立刻想到Webpack配置里边的devtool参数,以及对应的evaleval-cheap-source-map等等可选值以及它们的含义。除了知道不同参数之间的区别以及性能上的差异外,我们也可以一起了解一下sourceMap的实现方式。

sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中debug问题会带来非常糟糕的体验,sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率。sourceMap其实并不是Webpack特有的功能,而是Webpack支持sourceMap,像JQuery也支持souceMap

既然是一种源码的映射,那必然就需要有一份映射的文件,来标记混淆代码里对应的源码的位置,通常这份映射文件以.map结尾,里边的数据结构大概长这样:

{
  "version" : 3,                          // Source Map版本
  "file": "out.js",                       // 输出文件(可选)
  "sourceRoot": "",                       // 源文件根目录(可选)
  "sources": ["foo.js", "bar.js"],        // 源文件列表
  "sourcesContent": [null, null],         // 源内容列表(可选,和源文件列表顺序一致)
  "names": ["src", "maps", "are", "fun"], // mappings使用的符号名称列表
  "mappings": "A,AAAB;;ABCDE;"            // 带有编码映射数据的字符串
}
复制代码

其中mappings数据有如下规则:

  • 生成文件中的一行的每个组用“;”分隔;
  • 每一段用“,”分隔;
  • 每个段由1、4或5个可变长度字段组成;

有了这份映射文件,我们只需要在我们的压缩代码的最末端加上这句注释,即可让sourceMap生效:

//# sourceURL=/path/to/file.js.map
复制代码

有了这段注释后,浏览器就会通过sourceURL去获取这份映射文件,通过解释器解析后,实现源码和混淆代码之间的映射。因此sourceMap其实也是一项需要浏览器支持的技术。

如果我们仔细查看webpack打包出来的bundle文件,就可以发现在默认的development开发模式下,每个_webpack_modules__文件模块的代码最末端,都会加上//# sourceURL=webpack://file-path?,从而实现对sourceMap的支持。

sourceMap映射表的生成有一套较为复杂的规则,有兴趣的小伙伴可以看看以下文章,帮助理解soucrMap的原理实现:

Source Map的原理探究

Source Maps under the hood – VLQ, Base64 and Yoda

是否写过Loader?简单描述一下编写loader的思路?

从上面的打包代码我们其实可以知道,Webpack最后打包出来的成果是一份Javascript代码,实际上在Webpack内部默认也只能够处理JS模块代码,在打包过程中,会默认把所有遇到的文件都当作 JavaScript代码进行解析,因此当项目存在非JS类型文件时,我们需要先对其进行必要的转换,才能继续执行打包任务,这也是Loader机制存在的意义。

Loader的配置使用我们应该已经非常的熟悉:

// webpack.config.js
module.exports = {
  // ...other config
  module: {
    rules: [
      {
        test: /^your-regExp$/,
        use: [
          {
             loader: 'loader-name-A',
          }, 
          {
             loader: 'loader-name-B',
          }
        ]
      },
    ]
  }
}
复制代码

通过配置可以看出,针对每个文件类型,loader是支持以数组的形式配置多个的,因此当Webpack在转换该文件类型的时候,会按顺序链式调用每一个loader,前一个loader返回的内容会作为下一个loader的入参。因此loader的开发需要遵循一些规范,比如返回值必须是标准的JS代码字符串,以保证下一个loader能够正常工作,同时在开发上需要严格遵循“单一职责”,只关心loader的输出以及对应的输出。

loader函数中的this上下文由webpack提供,可以通过this对象提供的相关属性,获取当前loader需要的各种信息数据,事实上,这个this指向了一个叫loaderContextloader-runner特有对象。有兴趣的小伙伴可以自行阅读源码。

module.exports = function(source) {
    const content = doSomeThing2JsString(source);
    
    // 如果 loader 配置了 options 对象,那么this.query将指向 options
    const options = this.query;
    
    // 可以用作解析其他模块路径的上下文
    console.log('this.context');
    
    /*
     * this.callback 参数:
     * error:Error | null,当 loader 出错时向外抛出一个 error
     * content:String | Buffer,经过 loader 编译后需要导出的内容
     * sourceMap:为方便调试生成的编译后内容的 source map
     * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
     */
    this.callback(null, content);
    // or return content;
}
复制代码

更详细的开发文档可以直接查看官网的 Loader API。

是否写过Plugin?简单描述一下编写plugin的思路?

如果说Loader负责文件转换,那么Plugin便是负责功能扩展。LoaderPlugin作为Webpack的两个重要组成部分,承担着两部分不同的职责。

上文已经说过,webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务,从而实现自己想要的功能。

既然基于发布订阅模式,那么知道Webpack到底提供了哪些事件钩子供插件开发者使用是非常重要的,上文提到过compilercompilationWebpack两个非常核心的对象,其中compiler暴露了和 Webpack整个生命周期相关的钩子(compiler-hooks),而compilation则暴露了与模块和依赖有关的粒度更小的事件钩子(Compilation Hooks)。

Webpack的事件机制基于webpack自己实现的一套Tapable事件流方案(github)

// Tapable的简单使用
const { SyncHook } = require("tapable");

class Car {
    constructor() {
        // 在this.hooks中定义所有的钩子事件
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            brake: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
        };
    }

    /* ... */
}


const myCar = new Car();
// 通过调用tap方法即可增加一个消费者,订阅对应的钩子事件了
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
复制代码

Plugin的开发和开发Loader一样,需要遵循一些开发上的规范和原则:

  • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例;
  • 传给每个插件的 compilercompilation 对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件;
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住;

了解了以上这些内容,想要开发一个 Webpack Plugin,其实也并不困难。

class MyPlugin {
  apply (compiler) {
    // 找到合适的事件钩子,实现自己的插件功能
    compiler.hooks.emit.tap('MyPlugin', compilation => {
        // compilation: 当前打包构建流程的上下文
        console.log(compilation);
        
        // do something...
    })
  }
}
复制代码

更详细的开发文档可以直接查看官网的 Plugin API。

最后

本文也是结合一些优秀的文章和webpack本身的源码,大概地说了几个相对重要的概念和流程,其中的实现细节和设计思路还需要结合源码去阅读和慢慢理解。

Webpack作为一款优秀的打包工具,它改变了传统前端的开发模式,是现代化前端开发的基石。这样一个优秀的开源项目有许多优秀的设计思想和理念可以借鉴,我们自然也不应该仅仅停留在API的使用层面,尝试带着问题阅读源码,理解实现的流程和原理,也能让我们学到更多知识,理解得更加深刻,在项目中才能游刃有余的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/540708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java汽车4S店管理系统myeclipse定制开发oracle数据库网页模式java编程jdbc

一、源码特点 java汽车4S店管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助 oracle数据库,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 java汽车4S店管理系统myeclipse定制开发orac 二、功能介绍 此次系统…

漏扫工具-xray 1.9.10(文末附下载)

一、工具介绍 一款功能强大的安全评估工具 二、使用说明 1.使用基础爬虫爬取并对爬虫爬取的链接进行漏洞扫描 xray webscan --basic-crawler http://example.com --html-output vuln.html 2.使用 HTTP 代理进行被动扫描 xray webscan --listen 127.0.0.1:7777 --html-outp…

Fiddler如何抓取微信小程序的包

1.简介 有些小伙伴或者是童鞋们说小程序抓不到包,该怎么办了???其实苹果手机如果按照宏哥前边的抓取APP包的设置方式设置好了,应该可以轻松就抓到包了。那么安卓手机小程序就比较困难,不是那么友好了。所以…

FMC子卡设计资料原理图450-基于ADRV9009的双收双发射频FMC子卡 数字信号处理卡 射频收发卡 基站应用 便携测试设备

FMCJ450-基于ADRV9009的双收双发射频FMC子卡 一、板卡概述 ADRV9009是一款高集成度射频(RF)、捷变收发器,提供双通道发射器和接收器、集成式频率合成器以及数字信号处理功能。这款IC具备多样化的高性能和低功耗组合,FMC子卡为2路输入,…

MySQL高级_第08章_索引的创建与设计原则

MySQL高级_第08章_索引的创建与设计原则 1. 索引的声明与使用 1.1 索引的分类 MySQL 的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 从 功能逻辑 上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索…

新手如何重装Win10系统 新手重装Win10系统的方法

电脑系统是电脑运行的核心,如果出现问题就需要重装系统。对于新手来说,重装电脑系统可能会显得比较困难和陌生。本文将介绍新手如何重装电脑系统Win10,让电脑新手也能轻松搞定。 新手重装Win10系统的方法 一、准备工作 1、下载极客狗电脑重…

canvas、svg的基本使用【数据可视化】

什么是数据可视化? 基本概念:是关于数据视觉表现形式的科学技术研究 这个概念向我们传达了两个信息: (1)数据可视化是一门学科 (2)数据可视化与数据和视觉有关 数据可视化简单理解,…

veth网卡的多队列及RPS

背景: 3.10内核下容器使用的veth网卡,默认开启的是一个队列,导致在某些单线程多TCP链接的应用场景下,出现某个CPU软中断高的情况。之前处理的方案一直是开启这个veth网卡的RPS,让其在多流场景下可以去分散到其它CPU上…

DSSM - 双塔经典模型(微软)

《Learning Deep Structured Semantic Models for Web Search using Clickthrough Data》论文由微软发表于 CIKM-2013。DSSM被广泛用于工业界的 召回/粗排 阶段。 模型结构 模型结构一目了然,非常简单,双塔结构:user侧一个塔,ite…

ChatGPT的兴起的时代,国内chatgpt产品大盘点

在人工智能技术的不断发展和应用下,自然语言处理技术成为了研究的热点之一。而其中最受关注的就是“聊天机器人”技术,而GPT(Generative Pre-trained Transformer)模型则是目前最流行的聊天机器人生成模型之一。 随着 ChatGPT 技…

蓝牙RSSI/BLE AOA/UWB室内定位技术哪个好?

蓝牙AOA定位技术的出现,弥补了蓝牙RSSI值定位精度不高的缺陷。从理论上来说,可以对目前的蓝牙RSSI定位方案进行一定程度的替代。当然了,在高精度定位应用领域中,UWB定位已经在批量的成熟商用了。蓝牙AOA也具有很高的定位精度&…

单位网站被黑被下达整改进行行政处罚

最近这几年,由于信息系统安全等级保护法的普及,越来越多公司收到当地公安网监部门打来的电话,说你们公司网站有漏洞,需要限期在2-3内进行漏洞整改和加固,遇到这种情况,不要着急,下面来分享一下该…

JavaEE(系列8) -- 多线程案例(单例模式)

目录 1. 设计模式 2. 单例模式 -- 饿汉模式 3. 单例模式 -- 懒汉模式 4. 单例模式(懒汉模式-多线程) 1. 设计模式 什么是设计模式? 设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路…

【融合感知】激光雷达和相机融合感知-BEVFusion

BEVFusion有两篇文章,这里在一起分析下不同,分别是: 【1】BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework. 【2】BEVFusion: Multi-Task Multi-Sensor Fusion with Unified Bird’s-Eye View Representation 先说结论&…

品牌联名又出圈了!小红书数据揭示,引爆流量三部曲

这几天,你们的朋友圈是不是被喜茶FENDI黄刷屏啦?近日,茶饮品牌牵手意大利奢侈品牌联名上新,一跃成为各平台热门。 品牌新联名,这次又出圈了! 喜茶可谓是联名界的老玩家了,曾与藤原浩、《梦华录》…

HTML5新增标签

前言 HTML5的新特性主要是针对以前的不足,增加了一些新标签,新表单和新表单属性 但是呢!这些标签大多都存在兼容性的问题,基本是IE9以上的版本才支持 之前我们做布局,都用的是div,但是没有语义性 HTM…

Java开发的多商户商城系统源码小程序app

Java开发的多商户商城系统源码小程序app主要是为商家提供一个线上销售平台,实现商品展示、订单管理、支付等功能,并且支持多商户注册,以下是它的功能介绍: 一、 用户端:1. 用户注册与登录:用户可以使用手机…

关于LimeReport导出报告在Qt下的使用问题

关于这个第三方库的使用网上有很多资料了 具体说一下关于文本和图片控件的使用 1、文本使用 如果需要自定义文本内容,需要的格式为上图$V{}; 2、图片使用 如果需要自定义图片内容,需要的格式为上图$V{},写法和文本内容一样; 3、…

博士毕业答辩流程 注意事项

前言:2023年5月17日14:00-17:00,与实验室其他同学一起旁听了本实验室的博士论文答辩。接下来,我对博士毕业答辩的大致流程进行简要介绍,并对个环节的注意事项进行总结归纳,供毕业生参考。 目录 1. 准备阶段2. 汇报期间…

Python 框架学习 Django篇 (一) 安装及基本使用

环境说明 python 3.11.3 Django 4.2.1 idea 2023.1 一、安装调试 我这里默认idea和python环境都是装好的,直接从建项目开始 新建项目 项目名称: demo 安装Django //配置清华镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simp…