从头发的浓密程度和干练的走路姿式我察觉到,面前坐着的这位面试官也是一把好手。我像以往同样,准备花3分钟的时间进行自我介绍。在此期间,个人目光被16寸的MacBook Pro所吸引,此次的自我介绍我作足了准备,颇有信心征服面试官。不出我所料,面试官被我引入了我擅长的领域。javascript
看来你对Webpack很熟悉,那我来考考你
(我开始熟悉的报起了菜名)css
raw-loader
:加载文件原始内容(utf-8)file-loader
:把文件输出到一个文件夹中,在代码中经过相对 URL 去引用输出的文件 (处理图片和字体)url-loader
:与 file-loader 相似,区别是用户能够设置一个阈值,大于阈值时返回其 publicPath,小于阈值时返回文件 base64 形式编码 (处理图片和字体)source-map-loader
:加载额外的 Source Map 文件,以方便断点调试svg-inline-loader
:将压缩后的 SVG 内容注入代码中image-loader
:加载而且压缩图片文件json-loader
加载 JSON 文件(默认包含)handlebars-loader
: 将 Handlebars 模版编译成函数并返回babel-loader
:把 ES6 转换成 ES5ts-loader
: 将 TypeScript 转换成 JavaScriptawesome-typescript-loader
:将 TypeScript 转换成 JavaScript,性能优于 ts-loadersass-loader
:将SCSS/SASS代码转换成CSScss-loader
:加载 CSS,支持模块化、压缩、文件导入等特性style-loader
:把 CSS 代码注入到 JavaScript 中,经过 DOM 操做去加载 CSSpostcss-loader
:扩展 CSS 语法,使用下一代 CSS,能够配合 autoprefixer 插件自动补齐 CSS3 前缀eslint-loader
:经过 ESLint 检查 JavaScript 代码tslint-loader
:经过 TSLint检查 TypeScript 代码mocha-loader
:加载 Mocha 测试用例的代码coverjs-loader
:计算测试的覆盖率vue-loader
:加载 Vue.js 单文件组件i18n-loader
: 国际化cache-loader
: 能够在一些性能开销较大的 Loader 以前添加,目的是将结果缓存到磁盘里更多 Loader 请参考官网html
(面试官:挺好,知道的还挺多)vue
(这大兄弟好像听上瘾了,继续开启常规操做)java
define-plugin
:定义环境变量 (Webpack4 以后指定 mode 会自动配置)ignore-plugin
:忽略部分文件html-webpack-plugin
:简化 HTML 文件建立 (依赖于 html-loader)web-webpack-plugin
:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用uglifyjs-webpack-plugin
:不支持 ES6 压缩 (Webpack4 之前)terser-webpack-plugin
: 支持压缩 ES6 (Webpack4)webpack-parallel-uglify-plugin
: 多进程执行代码压缩,提高构建速度mini-css-extract-plugin
: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)serviceworker-webpack-plugin
:为网页应用增长离线缓存功能clean-webpack-plugin
: 目录清理ModuleConcatenationPlugin
: 开启 Scope Hoistingspeed-measure-webpack-plugin
: 能够看到每一个 Loader 和 Plugin 执行耗时 (整个打包耗时、每一个 Plugin 和 Loader 耗时)webpack-bundle-analyzer
: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)更多 Plugin 请参考官网node
(Double Kill)react
(就知道你会问这个,我用手掩盖着嘴角的微笑)webpack
Loader
本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。由于 Webpack 只认识 JavaScript,因此 Loader 就成了翻译官,对其余类型的资源进行转译的预处理工做。nginx
Plugin
就是插件,基于事件流框架Tapable
,插件能够扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 能够监听这些事件,在合适的时机经过 Webpack 提供的 API 改变输出结果。web
Loader
在 module.rules 中配置,做为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
Plugin
在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都经过构造函数传入。
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行如下流程:
初始化参数
:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数开始编译
:用上一步获得的参数初始化 Compiler 对象,加载全部配置的插件,执行对象的 run 方法开始执行编译肯定入口
:根据配置中的 entry 找出全部的入口文件编译模块
:从入口文件出发,调用全部配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到全部入口依赖的文件都通过了本步骤的处理完成模块编译
:在通过第4步使用 Loader 翻译完全部模块后,获得了每一个模块被翻译后的最终内容以及它们之间的依赖关系输出资源
:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每一个 Chunk 转换成一个单独的文件加入到输出列表,这步是能够修改输出内容的最后机会输出完成
:在肯定好输出内容后,根据配置肯定输出的路径和文件名,把文件内容写入到文件系统在以上过程当中,Webpack
会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,而且插件能够调用 Webpack 提供的 API 改变 Webpack 的运行结果。
简单说
(这道题还蛮注重实际,用户的体验仍是要从小抓起的)
webpack-dashboard
:能够更友好的展现相关打包信息。webpack-merge
:提取公共配置,减小重复配置代码speed-measure-webpack-plugin
:简称 SMP,分析出 Webpack 打包过程当中 Loader 和 Plugin 的耗时,有助于找到构建过程当中的性能瓶颈。size-plugin
:监控资源体积变化,尽早发现问题HotModuleReplacementPlugin
:模块热替换source map
是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具有良好的可读性,想要调试源码就须要 soucre map。
map文件只要不打开开发者工具,浏览器是不会加载的。
线上环境通常有三种处理方案:
hidden-source-map
:借助第三方错误监控平台 Sentry 使用nosources-source-map
:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高sourcemap
:经过 nginx 设置将 .map 文件只对白名单开放(公司内网)注意:避免在生产中使用inline-
和eval-
,由于它们会增长 bundle 体积大小,并下降总体性能。
Webpack 实际上为每一个模块创造了一个能够导出和导入的环境,本质上并无修改 代码的执行逻辑,代码执行顺序与模块加载顺序也彻底一致。
在发现源码发生变化时,自动从新构建出新的输出文件。
Webpack开启监听模式,有两种方式:
缺点:每次须要手动刷新浏览器
原理:轮询判断文件的最后编辑时间是否变化,若是某个文件发生了变化,并不会马上告诉监听者,而是先缓存起来,等aggregateTimeout
后再执行。
module.export = { // 默认false,也就是不开启 watch: true, // 只有开启监听模式时,watchOptions才有意义 watchOptions: { // 默认为空,不监听的文件或者文件夹,支持正则匹配 ignored: /node_modules/, // 监听到变化发生后会等300ms再去执行,默认300ms aggregateTimeout:300, // 判断文件是否发生变化是经过不停询问系统指定文件有没有变化实现的,默认每秒问1000次 poll:1000 } }
(敲黑板,这道题必考)
Webpack
的热更新又称热替换(Hot Module Replacement
),缩写为HMR
。这个机制能够作到不用刷新浏览器而将新变动的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 须要更新的部分),实际上 WDS 与浏览器之间维护了一个Websocket
,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差别后会向 WDS 发起Ajax
请求来获取更改内容(文件列表、hash),这样客户端就能够再借助这些信息继续向 WDS 发起jsonp
请求获取该chunk的增量更新。
后续的部分(拿到增量更新以后如何处理?哪些状态该保留?哪些又须要更新?)由HotModulePlugin
来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader
和vue-loader
都是借助这些 API 实现 HMR。
(面试官:不错不错,小伙子表达能力不错)
(基操,勿6)
VSCode
中有一个插件Import Cost
能够帮助咱们对引入模块的大小进行实时监测,还可使用webpack-bundle-analyzer
生成bundle
的模块组成图,显示所占体积。
bundlesize
工具包能够进行自动化资源体积监控。
文件指纹是打包后输出的文件名的后缀。
Hash
:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改Chunkhash
:和 Webpack 打包的 chunk 有关,不一样的 entry 会生出不一样的 chunkhashContenthash
:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变设置 output 的 filename,用 chunkhash。
module.exports = { entry: { app: './scr/app.js', search: './src/search.js' }, output: { filename: '[name][chunkhash:8].js', path:__dirname + '/dist' } }
设置 MiniCssExtractPlugin 的 filename,使用 contenthash。
module.exports = { entry: { app: './scr/app.js', search: './src/search.js' }, output: { filename: '[name][chunkhash:8].js', path:__dirname + '/dist' }, plugins:[ new MiniCssExtractPlugin({ filename: `[name][contenthash:8].css` }) ] }
设置file-loader的name,使用hash。
占位符名称及含义
const path = require('path'); module.exports = { entry: './src/index.js', output: { filename:'bundle.js', path:path.resolve(__dirname, 'dist') }, module:{ rules:[{ test:/\.(png|svg|jpg|gif)$/, use:[{ loader:'file-loader', options:{ name:'img/[name][hash:8].[ext]' } }] }] } }
可使用enforce
强制执行loader
的做用顺序,pre
表明在全部正常 loader 以前执行,post
是全部 loader 以后执行。(inline 官方不推荐使用)
(这个问题就像能不能说一说「从URL输入到页面显示发生了什么」同样)
(我只想说:您但愿我讲多长时间呢?)
(面试官:。。。)
高版本
的 Webpack 和 Node.js多进程/多实例构建
:HappyPack(不维护了)、thread-loader压缩代码
图片压缩
缩小打包做用域
:
提取页面公共资源
:
DLL
:
充分利用缓存提高二次构建速度
:
Tree shaking
Scope hoisting
动态Polyfill
更多优化请参考官网-构建性能
代码分割的本质其实就是在源代码直接上线
和打包成惟一脚本main.bundle.js
这两种极端方案之间的一种更适合实际场景的中间状态。
阿卡丽:荣耀剑下取,均衡乱中求
「用可接受的服务器性能压力增长来换取更好的用户体验。」
源代码直接上线:虽然过程可控,可是http请求多,性能开销大。
打包成惟一脚本:一把梭完本身爽,服务器压力小,可是页面空白期长,用户体验很差。
(Easy peezy right)
Loader 支持链式调用,因此开发上须要严格遵循“单一职责”,每一个 Loader 只负责本身须要负责的事情。
Loader的API 能够去官网查阅
加载本地 Loader 方法
webpack在运行的生命周期中会广播出许多事件,Plugin 能够监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。
Plugin的API 能够去官网查阅
找出合适的事件点去完成想要的功能
大多数JavaScript Parser遵循estree
规范,Babel 最初基于acorn
项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:
解析:将代码转换成 AST
转换:访问 AST 的节点进行变换操做生产新的 AST
想了解如何一步一步实现一个编译器的同窗能够移步 Babel 官网曾经推荐的开源项目the-super-tiny-compiler
面试官:(我听的口渴了,我们休息一会,一会进行下半场)
面试官拿起旁边已经凉透的龙井,喝了一口。
(这小伙子有点东西)
参考