[真 ✺ 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见)

相关推荐
weixin_3823952312 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__12 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.13 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~14 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华15 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子17 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅17 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni18 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学19 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端