【面试通关宝藏】Webpack篇

·  阅读 733
【面试通关宝藏】Webpack篇

🧩序言

在前端的面试中, webpack 也是面试官考察的重点之一。如果说候选人不会 webpack ,从某种层面上来说不会特别影响面试官最终的面试结果。但从某种意义上来说,候选人给面试官的印象分就会稍微折扣了一点点。所以呢,以不变应万变,还是抓紧时间复盘学习,应变面试官有可能涉及到的各种问题。

那么在接下来的这篇文章当中呢,将梳理 webpack 的基础知识,以及将对常见面试题进行汇总和解答。一起来学习吧~😝

🎨一、基础知识学习

我们先用一张思维导图来对 webpack 的一些基础知识进行总结归纳。详情见下图👇

webpack基础知识思维导图

对于以上内容,周一有整理过 5 篇文章。戳webpack基础知识传送门

大家可以到专栏进行学习查看,同时,如果想对 webpack 有一个系统一点的学习的话,推荐给大家可以直接看 《Webpack实战:入门、进阶与调优》这本书。

这本书相对来说对入门选手会友好一点,也是周一刚学习时看的一本书。其他书我也还没看过……等以后再来补充。

基础知识学会了,那么我们继续来看下比较常见的一些面试题汇总和解答~

🎲二、常见面试题汇总

同样地,我们先用一张思维导图来了解 webpack 中常见的一些面试题。详情见下图👇

webpack常见面试题汇总

现在我们来对这些问题进行一一解答。

🎯三、构建和打包

1、前端代码如何进行构建和打包?

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数: 从配置文件和 Shell 语句中q去读取与合并参数,得出最终的参数;
  • 开始编译: 用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口: 根据配置中的 entry 找出所有的入口文件;
  • 编译模块:入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译: 在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk ,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成: 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统;

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 来改变 Webpack 的运行结果。

简单来说:

  • 初始化参数: 启动构建,读取与合并配置参数,加载 Plugin ,实例化 Compiler
  • 编译模块:Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module ,递归地进行编译处理;
  • 完成输出: 将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中。

2、前端为何要进行打包和构建?

从代码层面来说:

  • 进行构建和打包后的项目体体积更小( Tree-Shaking 、压缩代码、合并代码,加载更快);
  • 可以对高级语言或语法进行编译(TSES6+ ,模块化、 scss );
  • 对代码的兼容性和错误进行检查( polyfillpostcsseslint )。

从研发层面来说:

  • 使得研发有着更统一、高效的开发环境;
  • 让项目组有着统一的构建流程和产出标准;
  • 在提测和上线等流程中慢慢集成公司的构建规范。

3、webpack原理

  • 首先需要先解析入口文件 entry ,使用@babel/parser ,将其转为 AST(抽象语法树)
  • 使用 @babel/traverse 插件,去找出入口文件中的所有 依赖模块
  • 然后使用 @babel/core+@babel/preset-env 插件,将入口文件的 AST 转为 Code
  • 2 中找到的入口文件的依赖模块,进行 遍历递归 ,重复执行 1,2,3
  • 重写 require 函数,并与 4 中生成的 递归关系图 一起,输出到 bundle 中。

webpack原理

🎰四、模块相关

1、module chunk bundle 分别是什么意思,有何区别?

  • module - 代表各个源码文件webpack 中一切皆模块;
  • chunk - 表示多模块合并成的,如 entryimport()splitChunk
  • bundle - 表示最终的输出文件。

2、source map是什么?开发环境和生产环境如何使用?

source map 是将编译打包压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre mapmap 文件只要不打开开发者工具,浏览器是不会加载的。下面给出在开发环境和生产环境下最适用的方式。

开发环境:cheap-module-eval-source-map

生产环境:cheap-module-source-map

注意点: 避免在生产中使用 inline-eval- ,因为它们会增加 bundle 体积大小,并降低整体性能。

3、如何引用一个自己编写的库lib(第三方模块)

  • output 中配置 filenamelibrary 以及 libraryTarget 等项,来引用自己编写的第三方库;

  • 解决库引用冲突:externals

🧶五、loader和plugin

1、webpack中常见的loader有哪些

  • file-loader:常用于处理图片和字体,旨在把文件输出到一个文件夹中,之后在代码中通过相对的 url 去引用输出的文件。
  • url-loader:常用于处理图片和字体,与 file-loader 类似,两个的区别在于,对于 url-loader 来说,用户可以设置一个阈值,当大于阈值时会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码。
  • css-loader:用于加载 CSS 文件,支持模块化压缩文件导入等特性。
  • style-loader:用于把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • sass-loader:用于将 SCSS/SASS 代码转换成 CSS 代码。
  • postcss-loader:解决兼容性问题,扩展 CSS 语法,使用下一代 CSS ,可以配合 autoprefixer 插件自动补齐 CSS3 的厂商前缀。

2、webpaack中常见的plugin有哪些

  • html-webpack-plugin:自动创建一个新的 html 文件,并把打包生成的 js 文件自动引入到这个 html 文件中。

  • clean-webpack-plugin:清空指定目录或者文件夹。

  • ignore-plugin:忽略部分文件。

  • splitChunkPlugin:对 js 文件进行代码分割。

  • mini-css-extract-plugin:对 css 文件进行代码分割,对于分割后的代码来说,支持按需加载。

  • optimize-css-assets-webpack-plugin:用于压缩 css 文件,减少 css 文件的大小。

3、loader和plugin的区别?

  • loader ,可以说是一个模块转换器,用于处理我们引用的模块。比如说,我们想要去引入一个 js 文件,或者是 css 格式的文件,都需要 loader 来帮助我们处理。
  • plugin ,可以说是一个扩展插件,它用于在打包过程中的某些时刻里生效。常见的有 htmlWebpackPlugincleanWebpackPlugin 等等。

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

  • 其本质为函数,函数中的 this 将会被作为上下文来提供给 webpack 填充,因此我们不能将 loader 设为一个箭头函数

  • 函数接受一个参数,这个参数为 webpack 传递给 loader 的文件源内容;

  • 函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息;

  • 函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer

代码如下所示:

// 导出一个函数,source为webpack传递给loader的文件源内容
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); // 异步
    return content; // 同步
}
复制代码

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

(1) 由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段来执行自己的插件任务。

(2) webpack 编译会创建两个核心对象:

  • compiler:包含了 webpack 环境种的所有配置信息,包括 optionsloaderplugin,和 webpack 整个生命周期相关的钩子。

  • compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建。

(3) 如果自己要实现 plugin ,也需要遵循一定的规范:

  • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问 compiler 实例。

  • 传给每个插件的 compilercompilation 对象都是同一个引用,因此不建议修改。

  • 异步的事件需要在插件处理完任务时去调用回调函数,之后通知 Webpack 进入下一个流程,否则会卡住。

(4) 实现plugin的模板如下:

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

🥁六、babel相关

1、babel和webpack的区别

  • babel ,编译 JS 新语法的一个工具,它不关心模块化
  • webpack ,是一个打包构建工具,是多个 loaderplugin 的集合,它关心模块化。

2、babel-polyfill和babel-runtime的区别

  • babel-polyfill ,旨在解决低版本浏览器无法兼容 ES6 的部分新的语法变量的问题,但它有一个缺点就是会污染全局,即 @babel/preset-env 会将 Promise 翻译成全局变量 var _Promise
  • babel-runtime不会污染全局babel-runtime 提供了单独的包,用以提供编译模块的工具函数。启用插件 babel-plugin-transform-runtime 后, Babel 就会使用 babel-runtime 下的工具函数。
  • 同时,值得注意的是,当引用自己编写的第三方库 lib 时,要用 babel-runtime

3、为何Proxy不能被Polyfill?

  • Proxy 没有办法被 polyfill ,也就是说低版本浏览器无法兼容一些新的语法变量,所以这也是我们常说的为啥 vue3 无法兼容低版本浏览器。

  • class 可以用 function 模拟, Promise 可以用 callback 来模拟,但 Proxy 的功能用 Object.defineProperty 就无法模拟了,所以 vue3 暂时无法减容低版本浏览器。

🥊七、性能优化相关

1、webpack如何实现懒加载?

  • import() 语法。

  • 使用 preloadingprefetching 来文件进行异步加载。

  • preloadprefetch 的区别在于: preload 是跟主文件同时进行加载,而不是在主文件加载完才加载的。一般来说,我们都用 prefetch , 只有等主文件把活干完了,再来加载剩余的我们想要的文件,这样的逻辑和对页面的优化才是比较完美的。

2、webpack常见性能优化(如何优化webpack的构建速度?)

(1)从开发环境的角度

生产环境优化:

  • 优化 babel-loader
  • 使用 IgnorePlugin ,排除掉一些不使用的模块,且用来缩小打包作用域
  • noParse;
  • happyPack(不维护了);
  • ParallelUglifyPlugin。

开发环境优化:

  • 自动刷新:使用 webpackDevServer 来实现自动刷新编译结果
  • 热模块更新HMR:热更新又称热替换, 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块
  • DllPlugin:使用 DllPlugin 进行分包,使用 DllReferencePlugin (索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。

(2)从产出代码的角度

  • 懒加载;
  • 提取公共代码;
  • 使用 cdn 加速;
  • IgnorePlugin
  • 使用 production
  • 使用 url-loader 对小图片进行 base64 编码;
  • 对生成的 bundle 文件加 hash 值;
  • Scope Hosting:
    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。
    • Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突.
    • 同时,必须是 ES6 的语法,因为有很多第三方库仍采用 CommonJS 语法。
    • 因此,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法。

🎬八、结束语

在上面的文章中,我们从构建和打包、模块相关、 loaderpluginbabel 、性能优化相关这五个方面,对 webpack 的一些常见面试题进行了归纳总结。相信通过上文的学习,大家对这一块的内容又有了一定的了解。关于 webpack 的常见面试题讲到这里就结束啦!希望对大家有帮助~

如文章有误或有想要补充的内容,欢迎留言或联系 vx:MondayLaboratory

最后就是祝各位看到这篇文章的小伙伴们,都能够斩获到自己心仪的 offer 呀~🥂🥂🥂

🐣彩蛋 One More Thing

(:参考资料

👉再来一打Webpack面试题

👉腾讯面试官:兄弟,你说你会Webpack,那说说他的原理?

👉面试官:说说Webpack中Loader和Plugin的区别?编写Loader,Plugin的思路?

(:pdf获取

👉 微信关注公众号 星期一研究室 ,点击下方导航栏 面试专栏简要 查看关键字获取~

(:更新地址

👉 offer来了面试专栏

(:番外篇

  • 关注公众号星期一研究室,第一时间关注优质文章,更多精选专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋
分类:
前端
标签: