webpack 与 grunt、gulp 的区别
grunt、gulp 是基于任务运行的工具:它们会自动执行指定的任务,就像流水线,把资源放上去然后通过不同插件进行加工,它们包含活跃的社区,丰富的插件,能方便的打造各种工作流。
webpack 是基于模块化打包的工具:webpack 把一切都当做模块,当 webpack 处理程序时,会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有的模块打包成一个或者多个 bundle。
因此这是两类完全不同性质的工具,而 npm script 也可以打造任务流。
webpack、rollup、parcel 优劣
webpack 适用于大型复杂的前端站点构建:webpack 有强大的 loader 和插件生态,打包后的文件实际上就是一个立即执行函数,这个立即执行函数接收一个参数(模块对象),键为各个模块的路径,值为模块内容。立即函数内部处理模块之间的引用、执行模块等,更适合文件依赖复杂的应用开发。
rollup 适用于基础库的打包,比如说vue、d3等。rollup 将各个模块打包进一个文件中,并通过 Tree-shaking 来删除无用的代码,可以最大程度上降低代码体积,但是 rollup 没有 webpack 那么多的代码分割、按需加载等功能,更加适合库的开发。
parcel 适用于简单的实验性项目:可以满足低门槛的快速看到效果,但是生态差、报错信息不够全面都是问题,除了一些实验性项目不建议使用。
常见的 loader
file-loader把文件输出到一个文件夹中,在代码中通过相对 URL 引用输入的文件url_loader和 file-loader 类似,能在文件很小的情况下以 base64 的方式将文件内容注入到代码中source-map-loader加载额外的 Source Map 文件,方便断点调试image-loader加载并压缩图片文件babel-loader将 ES6 转换为 ES5css-loader加载 css,支持模块化、压缩、文件导入等特性style-loader把 css 代码注入到 JavaScript 中,通过 DOM 操作去加载 csseslint-loader通过 ESLint 检查 JavaScript 代码html-minify-loader压缩HTML
loader 特性
- loader 从右到左地取值(evaluate)/执行(execute)
- loader 支持链式传递,链中的每个 loader 会将转换应用在已处理过的资源上
- loader 也可以内联显示指定
- loader 可以是同步的,也可以是异步的
- loader 运行在 Node.js 中,并且能够执行任何 Node.js 能做到的操作
- loader 可以通过 options 对象配置
- 除了常见的通过
package.json的 main 来将一个 npm 模块导出为 loader,还可以在module.rules中使用 loader 字段直接引用一个模块 - loader 能够产生额外的任意文件
常见的 plugin
define-plugin定义环境变量html-webpack-plugin简化 html 文件创建uglifyjs-webpack-plugin通过 UglifyES 压缩 ES6 代码webpack-parallel-uglify-plugin多核压缩,提高压缩速度webpack-bundle-analyzer可视化 webpack 输出文件的体积mini-css-extract-pluginCSS 提取到单独的文件中,支持按需加载clean-webpack-plugin在每次构建前清理 /dist 文件夹
loader 和 plugin 的不同
作用不同
loader 为加载器,webpack 将一切文件视为模块,但是 webpack 原生只能解析 js 和 json 文件,如果想将其他文件也打包的话,就会用到 loader,因此,loader 的作用是让 webpack 可以加载和解析非 JavaScript 文件。
plugin 为插件。可以扩展 webpack 的功能,让 webpack 更灵活。在 webpack 的运行周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
用法不同
loader 在 module.rules 中配置,即作为模块的解析规则存在。类型为数组,每一项都是一个 object,里面描述了对于什么类型的文件是用什么加载和使用的参数。
plugin 在 plugins 中单独配置。类型为数组,每一项是一个 plugin 的实例,参数都是通过构造函数传入。
bundle、chunk、module
bundle 由 webpack 打包出来的文件
chunk 代码块,一个 chunk 由多个模块组合而成,用于代码的合并和分割
module 开发中的单个模块,webpack 中一切皆模块,一个模块对应一个文件
webpack 的构建流程
webpack 的运行流程是一个串行的过程,从启动到结束会一次执行以下流程:
- 初始化参数:从配置文件和脚本语句中读取与合并参数,得出最终的参数;
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
- 确定入口:根据配置中的 entry 找出所有的入口文件;
- 编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤,直到所有入口文件依赖的文件都经过了本步骤的处理;
- 完成模块编译:第4步翻译完成所有模块后,得到了每个模块被翻译后的最终内容以及它们的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,将文件内容写入到文件系统。
在以上过程中,webpack 会在特定的时间点广播出特定的时间,插件在监听到事件后,会执行特定的逻辑,并且插件可以调用 webpack 提供的 API 改变 webpack 的运行结果。
是否自己写过 loader 和 plugin
loader 把读到的源文件内容转义成新的文件内容,并且每个 loader 通过链式操作,将源文件转换成想要的样子。
编写 loader 的时候要遵循单一原则,每一个 loader 只做一种转义工作,loader 拿到的是源文件内容(source),处理后可以通过返回值的方式将处理后的内容输出,也可以调用 this.callback() 方法将内容返回给 webpack。还可以通过 this.async() 生成一个 callback 函数,再用这个 callback 将处理后的内容输出出去。此外 webpack 还为开发者准备了开发 loader 的工具函数集 —— loader-utils
plugin 监听 webpack 运行中广播出的事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
webpack 的热更新是如何做到的
热更新又称作热替换(Hot 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,这里是浏览器刷新。wecpack-dev-server代码的工作,主要通过sockjs(webpack-dev-server的依赖)在浏览器端和服务器端建立一个websocket的长连接,将webpack编译打包的各个阶段的状态信息告知浏览器端,同时也包括第3步中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请求获取到最新的模块代码。HotModulePlugin会对新旧模块进行对比,决定是否更新模块,再决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。- 当
HMR失败后,回退到live reload操作,也就是进行浏览器刷新来获取最新打包代码。
webpack 优化前端性能
- 压缩代码:删除多余的代码、注释、简化代码的写法。可以使用
UglifyJsPlugin和ParallelUglifyPlugin来压缩 JS 文件,利用cssnano来压缩 css 代码。 - 利用 CDN 加速:在构建的过程中将引用的静态资源路径修改为 CDN 上对应的路径,可以使用
webpack对于output参数和各个loader的publicPath参数来修改资源路径。 Tree Shaking:将代码中永远不会走到的片段删除掉,可以在启动webpack时追加参数--optimize-minimize来实现。Code Splitting:将代码按照路由维度或者组件分块,可以做到按需加载,同时可以充分利用浏览器缓存。- 提取公共第三方库:
SplitChunksPlugin插件来进行公共模块抽取,利用浏览器缓存可以长期缓存这些无需频繁变动的公共代码。
提高 webpack 的打包速度
happypack:利用进程并行编译loader,利用缓存来使得rebuild更快,类似的替代者:thread-loader。- 外部扩展:将不怎么需要的第三方库脱离
webpack打包,减少打包时间。 dll:采用webpack的DllPlugin和DllReferencePlugin引入dll,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。- 利用缓存:
webpack.cache、babel-loader.cacheDirectory、HappyPack.cache都可以利用缓存提高rebuild的时间。 - 缩小文件搜索范围:比如
babel-loader,若文件仅存在于src中可以使用:include: path.resolve(__dirname, src)
提高 webpack 的构建速度
- 多入口情况下,使用
CommonsChunkPlugin来提取公共代码 - 通过
external配置来提取常用库 - 利用
DllPlugin和DllReferencePlugin预编译资源模块,通过DllPlugin来对哪些引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加再加载进来 - 使用
HappyPack实现多线程加速编译 - 使用
webpack-uglify-parallel来提升uglifyPlugin的压缩速度 - 使用
Tree-shaking和Scope Hoisting来剔除多余代码