[真 ✺ webpack原理] webpack-dev-server基本流程

之前都在了解webpack的build流程. 现在来了解下dev流程是怎么样的.

提示

本文需要一些webpack的前置知识, 如果文中有概念不理解的, 可以从这个链接复习: webpack运行流程.

目标

webpack的build结果是产出一堆文件.

dev的时候没有文件, 访问一个localhost就能预览代码结果.

我们的目标就是知道webpack-dev-server是如何做到这个效果的.

TL;DR

先总结, 后面的章节再看细节:

前置知识提要: build的api是compiler.run(), dev的api是devserver(option, compiler), 把compiler传给devserver.

webpack-dev-server会建立server, 让compiler的文件系统指向内存(memfs), 再运行compiler.

在收到请求的时候分析出文件名, 并从内存文件里读取, 返回给页面.

所以webpack-dev-server做的事就约等于: 先build, 再到dist目录起一个web容器.

在此基础上, 还开发了许多能力, 随手举例有:

  • 可以对compiler进行操作, 从而实现代码热更新. (开启watch模式并加载插件)
  • 接口http代理.
  • 选择协议. (http/https/http2)

到这里, 总结已经结束了. 接下来深入细节, 找到具体哪些关键代码完成了基本效果.

深入流程

webpack-dev-server启动的api是.start(), 就从这里开始看.

start()

去掉ipc, bonjour, log, listen等. 重要的流程有以下.

  • this.normalizeOptions(). 整理配置, 设置一些默认值.
  • this.initialize(). 初始化.
  • createWebSocketServer(). 创建ws连接. 与客户端的交互都是通过这里交互的. 即使自己没设置, 也会通过this.normalizeOptions()被设置默认值.

这里我们关心的部分是this.initialize(), 所以点到这个方法里去.

initialize()

这里也调用了一系列方法, 我会简单说下每个方法的效果, 并继续深入我们关心的方向.

  • 如果有ws, 加载3个插件: provide, hmr, progress.
  • setupHooks(). 在compiler的一些hook里更新自己状态, 并通过ws发送消息.
  • setupApp(). 新建express实例.
  • setupHostHeaderCheck(). 检查host, 开发的时候自己会改host来避免token跨域, 就在这个方法被拦的, 需要配置忽略host检查.
  • setupDevMiddleware(). 使用compiler和option准备好一个express的middleware. 这个方法会展开, 甚至还另起了个repo和npm包.
  • setupBuiltInRoutes(). 为express设置一些特殊路由的返回值. (通过url打开ide也是这里配置的)
  • watchFiles. 注册检测文件变化后进行的操作.
  • setupMiddlewares(). 为express设置middleware. 这个需要在下个部分展开.
  • createServer(). 根据配置用express起一个server. 这里会根据配置来决定起http还是https/http2.
  • 监测ctrl+c来调用stop方法, 和代理ws.

到这个步骤, server已经起起来了. 那么为什么我们可以访问到目标代码, 就要继续深入看middleware了.

setupDevMiddleware()

这个方法里, 为express设置了一系列middleware.

在列举这个方法中设置的middleware前, 先一句话介绍下express的middleware.

类似于redux的reducer, 每个请求的返回值会经过所有middleware瀑布式处理. (前面的返回是后面的输入)

要注意的是, 也和redux的middleware一样, middleware的执行是逆序的. (是不是用compose我没看)

下面开始列举这个方法里设置的middleware:

  1. 处理preflight请求的middleware.
  2. 根据配置, magicHtml的middleware.
  3. 根据配置, 处理静态资源的middleware.
  4. 根据配置, 处理historyApiFallback的middleware.
  5. 根据配置, 处理proxy的middleware.
  6. 在initialize步骤的setupDevMiddleware()方法准备好的middleware.
  7. 根据配置, 处理header的middleware.
  8. 根据配置, 压缩资源的middleware.

其中好几个是express内置的middleware. 而我们能从url中访问到需要的资源的关键, 在于setupDevMiddleware()就准备好的middleware. 下一节展开.

webpack-dev-middleware

js 复制代码
setupDevMiddleware() {
  const webpackDevMiddleware = require("webpack-dev-middleware");
​
  // middleware for serving webpack bundle
  this.middleware = webpackDevMiddleware(
    this.compiler,
    this.options.devMiddleware
  );
}

从注释就可以看出来, 我们想要知道的东西就在这里.

并且能猜出: 这个方法接受了compiler和options, 并且返回了一个express的middleware.

接下来的方法调用比较零碎, 大多处理一些细节, 以下就只描述调用webpackDevMiddleware()发生的重点了.

  1. 建立context变量, 用来保存compiler, option和一些状态.

  2. tap compiler的一些hook, 以更新context里的完成状态stats.

  3. 把compiler的outputFileSystem设为memfs.

  4. 调用compiler的watch()方法, 以调用compiler的.compile().

    因为在上一步已经把outputFileSystem设置为memfs, 所以compile的emit阶段就会调用memfs的api, 把文件写到内存里了.

    (进一步解释watch().compile()的流程: watch() => new Wathcing() => (constructor)_invalidate() => _go() => compile())

  5. 在middleware中, 先检查compile状态, 如果没编译好就返回wait until bundle finished (url). 直到compile完成. (通过步骤2tap的hook来更新stats变量)

  6. 在middleware中, 尝试用请求的url来映射文件名.

    如果映射到了文件名, 就从memfs中读取, 并返回.

接下来

大概了解了webpack-dev-server, 接下来就可以配合hmr再深一步了解, 还可以配合看一些loader是怎么对资源处理来配合hmr的. (下篇post见)

相关推荐
sTone873758 分钟前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端
蜡台9 分钟前
vue params传参刷新网页数据丢失解决方法
前端·javascript·vue.js
sTone873759 分钟前
Java 注解完全指南:从 "这是什么" 到 "自己写一个"
android·前端
文心快码BaiduComate15 分钟前
里程碑突破 | 文心快码中标国家开发银行代码研发助手项目
前端·后端·架构
霍理迪24 分钟前
axios的封装
前端
夜焱辰25 分钟前
CreatorWeave:一个本地优先的浏览器 AI 创作工作空间
前端·agent
wscqs26 分钟前
Superpowers 与 everything-claude-code 与 ui-ux-pro-max-skill 这些怎么合并起来一起用
前端
大转转FE30 分钟前
转转前端周刊第192期: 财务数仓 Claude AI Coding 应用实战
前端·人工智能
weixin_471383031 小时前
React Flow + Zustand 搭建工作流编排工作台
前端·react.js·前端框架
kilito_011 小时前
react疑难讲解
前端·react.js·前端框架