跳到主内容

前端webpack相关面试题(含答案)

webpack 作为前端最流行使用最广泛的构建工具,掌握 webpack 的基本知识也是非常重要。本文整理了前端面试常见的 webpack 相关的面试题,以及 webpack 面试题目相关的答案,希望对你有所帮助。

webpack 打包流程

  1. 初始化参数:从 shell 或者 配置文件中读取参数。
  2. 编译:创建 Compiler 对象,加载所有的插件,并且调用 Compiler.run() 方法开始执行。
  3. 确定入口:通过 entry 找到入口文件
  4. 编译模块:递归所有依赖的模块。形成关系树。通过 loader 进行编译转换最终的 JS 文件
  5. 输出文件:根据依赖关系树,将模块依次输出到文件中。

在整个打包过程,webpack 内部通过 Tapable 发布订阅者模式,让相关的 webpack 插件可以监听到打包过程中的事件,并且可以做一些额外的处理。

更多 Tapable 的使用方法,可以参考 Webpack 核心库 Tapable 的使用教程

webpack 构建过程中,compiler 和 compilation 对象的作用是什么

compiler 是全局对象,用于管理 webpack 的整个构建过程。

compilation 是每次构建的上下文对象,包含每次构建的所有信息,比如每次更新文件重新构建,compiler 都会创建一个新的 compilation 对象。

webpack 构建产物解析

webpack 构建最终产物是一个 IIFE 执行函数

(() => { // 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");
})

查看上面的代码可以看出,__webpack_modules__ 存放了编译后的各个文件模块的 JS 内容,__webpack_module_cache__ 用来做模块缓存,__webpack_require__ Webpack 内部实现的一套依赖引入函数。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。

为什么要实现 __webpack_require__ 函数,我们在模块化开发的时候,通常会使用 import 或者 require 引入依赖模块,webpack 打包编译的时候,会统一替换成自己的 __webpack_require__ 来实现模块的引入和导出,方便实现模块缓存机制,以及抹平模块规范之间的一些差异性。

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 去获取这份映射文件,通过解释器解析后,实现源码和混淆代码之间的映射。

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

TreeShaking 的原理

TreeShaking 就是在打包构建的时候,把项目中无用的代码移除掉,用来减少构建后的文件大小。

原理:基于 ES6 的模块特性对代码进行静态分析,确定无用代码并移除掉。通常把 import & export 标记为 3 类:

  • 所有 import 标记为 / harmony import /
  • 被使用过的 export 标记为 /harmony export([type])/,其中 [type] 和 webpack 内部有关,可能是 binding,immutable 等;
  • 没有被使用的 export 标记为 / unused harmony export [FuncName] /,其中 [FuncName] 为 export 的方法名,之后使用 Uglifyjs(或者其他类似的工具)进行代码精简,把没用的都删除。

为何 TreeShaking 依赖于 es6 的模块特性

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable 的

TreeShaking 的条件

  • 必须遵循 ES6 的模块规范 (import,export 语法) 来书写代码,如果是 CommonJS 规范 (require) 则无法使用。
  • 编写的模块代码不能有副作用,比如在代码内部改变了外部的变量则不会被移除。