Webpack及其生态

264 阅读9分钟

gulp & webpack

/* 一. 基本概念 */
  gulp基于task任务流(pipe)进行链式构建,打包或压缩等任务
  webpack是一个前端模块化方案,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源; 

/* 二.相同功能 */
  // 文件合并与压缩(css)
    gulp使用使用 gulp-minify-css 模块
    webpack样式合并一般用到 extract-text-webpack-plugin 插件,压缩则使用 webpack.optimize.UglifyJsPlugin
  // 文件合并与压缩(js)
    gulp使用使用 gulp-uglify 和 gulp-concat 两个模块
    webpack的js合并在模块化开始就已经做,压缩则使用 webpack.optimize.UglifyJsPlugin
  // sass/less预编译
    gulp使用 gulp-sass/gulp-less 模块
    webpack使用 sass-loader/less-loader 进行预处理
  // 启动server
    gulp使用 gulp-webserver 模块
    webpack使用 webpack-dev-server 模块
  // 版本控制
    gulp使用gulp-rev和gulp-rev-collector两个模块
    webpack将生成文件加上hash值
    
/* 三.两者区别 */
    gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。再次强调:基于任务task流pipe实现打包压缩等需求;
    webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能

有哪些常见的Loader?他们是解决什么问题的

postcss-loader  
添加前缀loader

file-loader
把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件

url-loader
和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去

source-map-loader
加载额外的 Source Map 文件,以方便断点调试

image-loader
加载并且压缩图片文件

babel-loader
把 ES6 转换成 ES5

css-loader
加载 CSS,支持模块化、压缩、文件导入等特性

style-loader
把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。

eslint-loader
通过 ESLint 检查 JavaScript 代码

有哪些常见的Plugin?他们是解决什么问题的

define-plugin
定义环境变量

commons-chunk-plugin
提取公共代码

uglifyjs-webpack-plugin
通过UglifyES压缩ES6代码

html-webpack-plugin   
html模板生成 css&js文件引入

extract-text-webpack-plugin   
js中的css样式抽离

HotModuleReplacementPlugin 
热更新配置

Loader和Plugin的不同

/* 一. 不同的作用 */
  Loader直译为"加载器"Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 
  所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。

  Plugin直译为"插件"Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件
  Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

/* 二. 不同的用法 */
  Loadermodule.rules中配置,也就是说他作为模块的解析规则而存在。
  类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件
  使用什么加载(loader)和使用的参数(options)

  Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

再次区分Loader和Plugin

LOADER (加载器:让webpack拥有了加载和解析非JavaScript文件的能力)

PLUGIN (插件:扩展webpack的功能)

webpack的构建流程是什么 ? 从读取配置到输出文件这个过程尽量说全

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

1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;

2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;

3. 确定入口:根据配置中的 entry 找出所有的入口文件;

4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;

7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

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

webpack的热更新是如何做到的?说明其原理

<!-- webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。 -->
<!-- 首先要知道server端和client端都做了处理工作 -->

1. 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。

2. 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。

3. 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。

4. 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。

5. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。

6. HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。

7. 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。

8. 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

如何利用webpack来优化前端性能?(提高性能和体验)

<!-- 用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。 -->

1. 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css

2. 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径

3. 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现

4. 提取公共代码。

如何提高webpack的构建速度

1. 多入口情况下,使用CommonsChunkPlugin来提取公共代码

2. 通过externals配置来提取常用库

3. 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。

4. 使用Happypack 实现多线程加速编译

5. 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度

6. 使用Tree-shaking和Scope Hoisting来剔除多余代码

什么是module,什么是chunk,什么是bundle

module:开发中的单个模块 ( 打包前,一般一个文件或页面视为一个module )

chunk:webpack在进行模块的依赖分析的时候,代码分割出来的代码块 ( 打包中 )

bundle:由webpack打包出来的文件 ( 打包后 )

webpack-dev-server和http服务器如nginx有什么区别

webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,他比传统的http服务对开发更加简单高效

happyPack开启多线程loader转换

运行在node.js之上的webpack时单线程模型,也就是只能一个一个文件进行处理,不能并行处理

happypack可以将任务分解给多个子进程,最后将结果发给主进程js是单线程模型,只能通过这种多线程的方式提高性能

webpack压缩代码体积

1. 去除不必要的插件
2. 代码压缩 webpack.optimize.UglifyJsPlugin
3. 设置缓存
4. 服务端压缩