webpack是一个静态模块打包器,他会递归的处理文件(模块)依赖关系,然后将程序所需的每一个模块打包成一个或多个bundle。

整体流程

  1. webpack以配置文件或者shell命令中的配置项为参数构建一个解析器complier,并且传入complier初始化插件,然后执行complier对象的run函数进行编译。
  2. 根据配置项中的entry属性读取(一个或多个)文件(模块),并通过loader翻译模块,分析翻译后的模块找到它的依赖模块,对依赖模块递归重复执行此操作。
  3. 根据前一步完成的全部模块的翻译结果和依赖关系进行代码整合,整合成一个个的chunk。
  4. 根据配置确定输出的路径和文件名,将内容写入文件系统,生成一个个的bundle。

概念

loader

  • loader是用来处理文件的,主要作用是将webpack不能识别的语法翻译成它能识别的js、json。
  • 本质是一个返回一个函数的node模块,这个函数的入参是代码,出参是处理后的代码。
  • loader的调用顺序是数组反向调用,从use数组的最后一个loader开始到第一个截止。

plugin

  • plugin是用来在webpack的编译过程中强化能力的,主要是在编译过程的各个阶段执行一些编译本身不用做的方法。
  • 本质是一个带apply方法的对象,写plugin的时候一般会实例化一下,例:new HtmlWebpackPlugin(),输出的就是一个带apply的对象。我们在上面也说到了,在流程再开始的时候,会传入x’s初始化插件,其实就是调用了插件的 apply(complier)。
  • 内部会按照代码逻辑处理complier,在complier挂载回调函数,这些函数会在webpack编译的生命周期中触发。

tree-shaking

tree-shaking是通过静态分析移除未被使用的引入依赖以减小打包体积的方案。而在commonjs模块化规范下,只有代码执行的时候才知道是否被引用,所以注定无法使用tree-shaking,而es6模块化规范出现后,编译时引入为静态分析提供了稳定的基石。
tree-shaking会失败的原因是你引入的模块是有副作用的,也就是它改动或使用了外部变量。
一些loader会将你的代码变为有副作用的的函数或者只能变成commonjs模块,这都会导致tree-shaking的失败。

优化

性能

  • 分析: 使用 webpack-bundle-analyzer插件分析打包结果
  • 合理代码分割:code splitting minChunks 分离第三方库和复用文件 懒加载
  • tree-shaking: webpack4设置mode为production时自动开启,其余详细见上文
  • 按需加载: babel-plugin-import
  • 压缩: webpack4默认terser-webpack-plugin 另外optimize-css-assets-webpack-plugin压缩css

    构建速度

  • 缩小范围:合理配置alias、includes、exclude

  • 多进程:HappyPack 建议大项目用,线程额外开销也很大
  • 抽离第三方模块:内置的DllPlugin 忽略不经常变更的vue、element等第三方,值打包本身的文件代码
  • 缓存:cache-loader

补充:一切的配置都不如升个版本,新版本yyds。

文件监听

轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。

热更新

先简述一下流程。这里涉及到:webpack、webpack-dev-server、浏览器

  1. webpack-dev-server中的 webpack-dev-middleware 中间件调用了 webpack的watch方法,监听原文件的变化,根据配置文件进行模块的重新打包,并将打包后的代码通过js对象保存在内存中。
  2. 在启动时 webpack-dev-server 就已经通过sockjs与浏览器端建立了一个webSocket长链接,并且监听webpack的compile的done事件,在compile完成后将新模块的hash值通过webSocket发送到浏览器。
  3. 同时, webpack-dev-server 本身还会在初始化的时候使用webpack-dev-server/client包添加到entry,这样在构建出来的包里就存在处理scoket请求的client代码了。
  4. 浏览器端接收socket发送过来的文件hash并存储,之后根据webpack/hot的配置决定是发送hash给webpack去做热更新还是直接重新刷新页面。(如果配置了hot,则将hash发送给webpack在浏览器端的代码去做热更新)
  5. 如果做热更新则 浏览器上的webpack/hot/dev-server会调用check方法作对比,check之后会调用 hotDownloadUpdateChunk 方法获取新hash值所对应的代码块,然后将代码块交个 webpack/lib/HotModuleReplacement.runtime 进行热更新。
  6. webpack/lib/HotModuleReplacement.runtime 中执行了hotApply方法,这个方法删除了更新前的代码缓存并将新的模块添加到modules中。 (如果在热更新过程中出现错误,热更新将回退到刷新浏览器)
  7. 在入口文件中调用 HMR 的 accept 方法,即将变化的文件的返回值插入到页面。

另外 vue-loader 中对于加载的热更新,vue-loader在内部也执行了 module.hot.accept() ,这部分在 vue-hot-reload-api 中有介绍。