webpack没有-谁选择放弃 Webpack? ——vite原理解析

前几天,有达在Vue 3.0 beta直播中提到了一款vite工具。 它的描述是:一个Vue单页面组件的非封装开发服务器,可以直接在浏览器中运行请求的Vue文件。 我对它的原理更感兴趣。 兴趣,所以体验了一下,写了这篇文章,主要是对vite实现原理的分析和一些思考。

初步知识

vite 稍微依赖于 sciprt 模块的特性,所以需要提前做好功课,参考:JavaScript 模块 - MDN。

模块 sciprt 允许直接在浏览器中运行本机支持模块

<script type="module">
    // index.js可以通过export导出模块,也可以在其中继续使用import加载其他依赖 
    import App from './index.js'
</script>

当遇到导入依赖时,会直接发起HTTP请求对应的模块文件。

开发环境

本文使用的版本是vite@0.3.2,附上github项目地址~这个项目其实每天晚上都在更新

首先克隆存储库

git clone https://github.com/vuejs/vite
cd vite && yarn

环境安装完毕后,在项目下创建examples目录,添加index.html和Comp.vue文件,这里直接使用README.md中的examples

第一个是 inindex.html

<div id="app"></div>
<script type="module">
import { createApp } from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app')
</script>

然后`Comp.vue`

<template>
  <button @click="count++">{{ count }} times</button>
</template>

<script>
export default {
  data() => ({ count0 })
}
</script>


<style scoped>
button { color: red }
</style>

然后在examples目录下运行

../bin/vite.js 

可以在浏览器中打开预览:3000,同时支持文件热更新~

如果需要调试源码,只需启动 npm run dev ,它会打开 tsc -w --p 监听 src 目录中的变化并实时输出到 dist 目录中,然后就可以启动快乐的源码时光~

入口文件

目前这个项目迭代非常频繁(昨天那个叫historyFallbackMiddleware的中间件还在,明天就没有了),但是大概的实现思路应该基本确定了,所以首先确定本次源码阅读的目标:了解如何在不使用webpack等工具的前提下直接运行vue文件进行打包。 基于这个目的,主要是了解实现思路,理清整体结构,而不是屈服于具体细节。

从入口 bin/vite.js 开始

const server = require('../dist/server').createServer(argv)

没有web凭据_webpack没有_没有web凭据怎么办

可以看到createServer方法,直接定位到src/server/client.tx。 Vite使用Koa构建服务器。 createServer中主要通过中间件注册相关函数。

// src/index.ts
// 提前预告这四个插件的作用
const internalPlugins: Plugin[] = [
  modulesPlugin, // 处理入口html文件script标签和每个vue文件的模块依赖
  vuePlugin, // vue单页面组件解析,将template、script、style解析成不同的响应内容,可以理解为简易版的vue-loader
  hmrPlugin, // 使用websocket实现文件热更新
  servePlugin // koa配置插件,目前看来主要是配置协商缓存相关
]

export function createServer({
  root = process.cwd(),
  middlewares: userMiddlewares = []
}: ServerConfig = {})
Server 
{
  const app = new Koa()
  const server = http.createServer(app.callback())
  // 预留了userMiddlewares方便提供后续API
  ;[...userMiddlewares, ...middlewares].forEach((m) =>
    m({
      root,
      app,
      server
    })
  )

  return server
}

Vite通过下面的中间件注册koa中间件,

export const modulesPlugin: Plugin = ({ root, app }) => {
  // 每个插件实际上是注册koa中间件
  app.use(async (ctx, next) => {})
}

看起来和Vue2的源码结构类似,通过装饰器逐步添加功能~目前只需要明确这四个插件的功能即可。

// vue2源码结构
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

模块解析器中间件

这个中间件的作用是编译index.html和SFC文件的内容,并处理相关的依赖关系。

比如前面的html文件的script标签内容会被编译成

import { createApp } from '/__modules/vue'// 之前是import { createApp } from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app'

这样webpack没有,当浏览器解析并运行该模块类型的script标签时,就会请求对应的模块文件,其中

没有web凭据_没有web凭据怎么办_webpack没有

对于入口文件,需要script标签下的相关依赖。 对于单页面组件,vue-loader中,tmplate、script、style标签也需要处理; 在 vite 中,这些依赖项将作为 css 和 js 文件请求加载。

单页组件主要包括template、script、style标签,script标签中代码的导入会被编译成

// 加载热更新模块客户端,后面会提到
import "/__hmrClient"

let __script; export default (__script = {
  data() => ({ count0 })
})
// 根据type进行区分,样式文件type=style
import "/Comp.vue?type=style&index=0"
// 保留css scopeID
__script.__scopeId = "data-v-92a6df80"
// render函数文件type=template
import { render as __render } from "/Comp.vue?type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"

style 和 template 标签将被重绘为 /Comp.vue?type=xxx,并且将重新发送 http 请求。 该方法通过query参数来区分并加载SFC文件各个模块的内容,与vue-loader中通过webpack中的resourceQuery配置处理方式相同。 如果你知道vue-loader的运行原理,你可能已经意识到了。 我写了一篇从vue-loader源码实现CSS-Scoped的文章,里面也介绍了vue-loader。 一般原则。

回到vite,现在我们了解了moduleResolverMiddleware的作用,主要是重画模块路径,并通过query参数区分SFC文件的依赖关系,以便浏览器可以通过url加载实际的模块。打开浏览器控制台查看特定文件请求

Vue插件

前面提到,单页面组件的模板和样式会被处理成单独的导入路径,通过query.type来区分,那么当服务器收到对应的url请求时如何返回正确的资源内容呢? 答案就在第二个插件VuePlugin中。

单页文件的请求有一个特点,就是以*.vue结尾作为请求路径。 服务器收到具有这些特征的http请求时,主要进行处理

{
 filename'/Users/Txm/source_code/vite/examples/Comp.vue',
 template: {
   type'template',
   content'n  n',
   loc: {
     source'n  n',
     start: [Object],
     end: [Object]
   },
   attrs: {},
   map: {
     version3,
     sources: [Array],
     names: [],
     mappings';AACA',
     file'/Users/Txm/source_code/vite/examples/Comp.vue',
     sourceRoot'',
     sourcesContent: [Array]
   }
 },
 script: {
   type'script',
   content'nexport default {n  data: () => ({ count: 0 })n}n',
   loc: {
     source'nexport default {n  data: () => ({ count: 0 })n}n',
     start: [Object],
     end: [Object]
   },
   attrs: {},
   map: {
     version3,
     sources: [Array],
     names: [],
     mappings';AAKA;AACA;AACA',
     file'/Users/Txm/source_code/vite/examples/Comp.vue',
     sourceRoot'',
     sourcesContent: [Array]
   }
 },
 styles: [
   {
     type'style',
     content'nbutton { color: red }n',
     loc: [Object],
     attrs: [Object],
     scopedtrue,
     map: [Object]
   }
 ],
 customBlocks: []
}

回去整理一下流程

没有web凭据怎么办_webpack没有_没有web凭据

// Comp.vue返回的文件内容,可以看见跟入口文件的script标签内容比较相似
import { updateStyle } from "/__hmrClient"

const __script = {
  data() => ({ count0 })
}
// style标签内容解析后的css代码
updateStyle("92a6df80-0""/Comp.vue?type=style&index=0")
__script.__scopeId = "data-v-92a6df80"
// temlpate标签内容解析后的render
import { render as __render } from "/Comp.vue?type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"
export default __script

每个标签的内容解析完毕后,会通过LRUCache进行缓存,方便最后复用

export const vueCache = new LRUCache<string, CacheEntry>({
  max: 65535
})

至此,我们对vite如何通过koa直接运行vue文件有了一个大概的了解。 它的思想和vue-loader类似。 它使用模块脚本来处理文件依赖关系,然后在解析后拼接不同的query.types来处理每个页面文件。 资源文件,最终响应浏览器进行渲染。

hmr插件

前面提到过,vite还支持文件的热更新。 既然没有使用webpack,那么应该怎么做呢? 答案是自己实现一个哈哈哈~

热更新主要通过webSocket实现,包括ws服务器和ws客户端两部分。 hmrPlugin主要负责ws服务器部分。 ws客户端在src/client.ts中实现,第一步处理模块依赖。 导入“/__hmrClient”将服务器与客户端关联起来。

目前主要定义了以下消息类型

当文件发生变化时,服务器会在handleVueSFCReload方法中根据变化的类型推送不同的消息。 当客户端收到相应的消息时,会结合vue.HMRRuntime进行处理或者重新加载新的资源。

这里还有很多TODO需要热更新。 我觉得这是学习热更新原理的一个很好的案例。 先编码一下,然后再回去读一遍。

关于热更新的原理,社区里有很多原理分析,不妨一步步看下去

没有web凭据怎么办_没有web凭据_webpack没有

服务插件

该插件主要用于实现一些koa请求和响应配置。

经过前面的分析,每次请求时,都会从入口文件开始,依次分析各个依赖关系

在上一步中,很明显,对于每个vue文件,都会发送多个http请求,然后搜索和解析的操作非常频繁。 如果不配置缓存,服务器的性能负担会比较大。 koa-conditional-get 和 koa-etag 应该就是解决这个问题的,不过好像还没有实现。

概括

至此,vite源码基本阅读完毕。 由于本地阅读源码的主要目的是了解整个工具的实现原理和大致功能,对于每个功能的实现细节并没有深入的了解。 几个重要的方法包括rewriteImports、compileSFCMain、compileSFCTemplate、compileSFCStyle、updateStyle等没有展示具体代码实现,主要收获是理解

当我第一次看到vite的介绍时,我就觉得这会是一个特别有趣的工具。 虽然还没发售,但我还是不忍心看。感觉主要效果是

目前看来,vite 还是缺少打包等重要功能,应该无法替代 webpack 等工具。 但我觉得vite不应该用来替代现有的开发工具,所以我大概不会添加打包之类的功能~

粉丝专属福利:关注公众号后回复“前端架构设计”,限时发一本书,每天仅限10本webpack没有,累计发放超过33000本。

您的点“看”是我前进的动力!