转载于: https://www.cnblogs.com/gaoht/p/11310365.html
转载方式: 手打
转载者: lemon-Xu
关于webpack的面试题
核心概念
- entry: 一个可执行模块或者库的入口
- chunk:多个文件组成一个代码块。可以将可执行的模块和他所依赖的模块组合成一个chunk,这是打包
- loader:文件转换器。例如把ES6转为ES5,scss转为css等。
- plugin:扩展webpack功能的插件。在webpack构建的生命周期节点上加入扩展hook,添加功能。
1. webpack与grunt、gulp的不同?
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
webpack是基于入口的。webpack会自动递归解析人口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来创展webpack功能。
所以总结一下:
-
从构建思路来说
gulp和grunt需要开发者将整个前端构建过程拆分成多个‘Task’,并合理控制所有‘Task’的调用关系,webpack需要开发者找到入口,并需要清楚对于不同的资源应该用什么Loader做何种解析和加工 -
对于知识背景来说
gulp更像后端开发者的思路,需要对于整个流程了如指掌,webpack更倾向于前端开发者的思路。
2. 与webpack类似的工具还有那些?谈谈你为什么最终选择(或放弃)使用webpack?
同样是基于入口的打包工具还有以下几个主流的:
- webpack
- rollup
- parcel
从应用场景上来看:
- webpack适用于大型复杂的前端站点构建
- rolup适用于基础库的打包,如vue、react
- parce适用于简单的实验性项目,它可以满足低门槛的快速看到效果
由于parcel在打包过程中给出的调试信息十分有限,所以一旦打包出错难以调试,所以不建议复杂的项目使用parcel
3. 有哪些常见的Loader?他们是解决什么问题的?
- file-loader: 把文件输出到一个文件夹中,在代码中通过URL去引用输出的文件。
- url-loader:和file-loader类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中去
- source-map-loader:加载额外的Source Map文件,以方便断点调试
- image-loader:加载并且压缩图片文件
- bael-loader:把ES6转换成ES5
- css-loader:加载CSS,支持模块化、压缩、文件导入等特性
- style-loader:把CSS代码注入到JavaScript中,通过DOM操作去加载CSS
- eslint-loader:通过ESLint检查JavaScript代码
4. 有哪些常见的Plugin?他们是解决什么问题的?
- define-plugin:定义环境变量
- commons-chunk-plugin:提供公共代码
- uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
5. Loader和Plugin的不同?
不同的作用
- Loader直译为‘加载器’。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。所以Loader的作用让webpack拥有了加载和解析非JavaScript文件的能力。
- Plugin直译为‘插件’。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。在Webpack运行的生命周期中会广播许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。
6. webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全
Webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
-
初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数;
-
开始编译: 用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译;
-
确定入口:根据配置中的entry找出所有的入口文件;
-
编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,在找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
-
完成模块编译:在经过第4步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及他们之间的依赖关系,根据entry配置生成代码块chunk;
-
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
-
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件会执行特定的逻辑,并且插件可以调用webpack提供的API改变webpakc的运行结果。
7. 是否写过Loader和Plugin?描述一下编写loader和plugin的思路?
Loader像一个“翻译官”把读到的源文件内容转义成新的文件内容,并且每个Loader通过链式操作,将源文件一步步翻译成想要的样子。
编写Loade时要遵循单一原则,每个Loader只做一种“转义”工作。每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。还可以通过this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。此外webpack还为开发者准备了开发loader的工具函数集—loader-utils。
相对于Loader而言,Plugin的编写就灵活了许多。webpack在运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。
8. webpack的热更新是如果做到的?说明其原理?
webpack的热更新又称热替换(Host Module Replacement),缩写为HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
首先要知道server端和client端都做了处理工作
-
第一步,在webpack的watch模式下,文件系统中某一个文件发生修改,webpack监听的文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的JavaScript对象保存在内存中。
-
第二步是webpack-dev-server和webpack之间的接口交互,而在这一步,主要是dev-server的中间件webpack-dev-middleware和webpack之间的交互,webpack-dev-middleware调用webpack暴露的API对代码变化进行监控,并且告诉webpack,将代码打包到内存中。
-
第三步是webpack-dev-server对文件改变的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase为true的时候,Server会监听这些配置文件中静态文件的变化,变化后会通知浏览器对应用进行live reload。注意,这是浏览器刷新,和HMR是两个概念。
-
第四步也是webpack-dev-server代码的工作,该步骤主要是通过sockjs(webpack-dev-server的依赖)在浏览器和服务端之间建立一个websocket长连接,将webpack编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中Server监听静态文件变化的信息。浏览器端根据这些socket消息进行不同的操作。当然服务端传递的最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换。
-
webpack-dev-server/client端并不能够请求更新的代码,也不会执行热更新模块操作,而把这些工作又交回了webpack,webpack/hot/dev-server的工作就是根据webpack-dev-server/client传给它的信息以及dev-server的配置决定是刷新浏览器还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
-
HotModuleReplacement.runtime是客户端HMR的中枢,他接收到上一步传递给他的新模块的hash值,它通过JsonpMainTemplate.runtime向server端发送Ajax请求,服务端返回一个json,该json包含了所有要更新的模块的hash值,获取到更新列表后,该模块再次通过jsonp请求,获取到最新的模块代码。这就是上图中7、8、9步骤。
-
而第10步是决定HMR成功与否的关键步骤,在该步骤中,HotModulePlugin将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
-
最后一步,当HMR失败后,回退到live reload操作,也就是进行浏览器刷新来获取最新打包代码。
9. 如何利用webpack来优化前端性能?(提供性能和体验)
用webpack优化前端是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。
- 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParalleUglifyPlugin来压缩JS文件,利用cssnano(css-loader?minimize)来压缩css
- 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以使用webpack对于output参数和各loader的publicPath参数来修改资源路径
- 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数-optmize-minimize来实现
- 提取公共代码。
10. 如何提高webpack的构建速度?
- 多入口情况下,使用CommonsChunkPlugin来提取公共代码
- 通过externals配置来提取常用库
- 利用DllPlugin和DllReferencePlugin预编译资源模块,通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行编译,再通过DllReferencePlugin将编译的模块加载进来。
- 使用Happypack实现多线程加速编译
- 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。原理上webpack-uglifty-parallel采用了多核并行压缩来提升压缩速度
- 使用Tree-shaking和Scope Hoisting来剔除多余代码
11. 怎么配置单页应用?怎么配置多页应用?
单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可,这里不再赘述。
多页应用的话,可以使用webpack的AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。多页应用中要注意的是:
- 每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表
- 随着业务的不断扩展,页面可能回不断的追加,所以一定要让入口的配置足够灵活吗,避免每次添加新页面还需要修改构建配置
12. npm打包需要注意那些?如何利用webpack来更好的构建?
Npm是目前最大的JavaScript模块仓库,里面有来自全世界开发者上传的可复用模块。你可能至少JS模块的使用者,但是有些情况你也会去选择上传自己开发的模块。关于NPM模块上传的方法可以去官网上进行学习,这里只讲解如何利用wepack来构建。
NPM模块需要注意一下问题:
- 要支持CommonJS模块化规范,所以要求打包后的最后结果也遵守该规则。
- Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。
- Npm包大小应该是尽量小(有些仓库会限制包大小)
- 发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。
- UI组件类的模块应该将依赖的其他资源文件,例如.css文件也需要包含在发布的模块里。
基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
- CommonJS模块化规范的解决方案:设置output.library Target = 'commonjs2'使输出的代码符合CommonJS2模块化规范,以共给其他模块导入使用
- 输出ES5代码的解决方案:使用babel-loader把ES6代码转换称ES5的代码。再通过开启devtool: ‘souce-map’输出SourceMap以发布调试。
- Npm包大小尽量小的解决方案:Babel在把ES6代码转换成ES5代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入tansform-runtime插件
- 不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack那些模块不需要打包。
- 对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下: