Webpack面试题

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_modulesmoduleId;

// 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());

总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值