尤大为何放弃Webpack?来探索一下 Vite

回复“2”加入前端群

本文同步在掘金博主:「橙红年代」个人博客shymean.com上。

掘金原文链接: https://juejin.im/post/5ea2361de51d454714428b44

预备知识


vite重度依赖module sciprt的特性,因此需要提前做下功课,参考:JavaScript modules 模块 - MDN。

module sciprt允许在浏览器中直接运行原生支持模块

当遇见import依赖时,会直接发起http请求对应的模块文件。

开发环境


本文使用的版本为vite@0.3.2,附github项目地址~目前这个项目貌似每天都在更新

首先克隆仓库

git clone https://github.com/vuejs/vite

cd vite && yarn

环境安装完毕后在项目下创建examples目录,新增index.htmlComp.vue文件,这里直接用README.md中的例子

首先是inidex.html

然后是`Comp.vue``

<button @click=“count++”>{{ count }} times

然后在exmples目录下运行

…/bin/vite.js

即可在浏览器http://localhost:3000打开预览,同时支持文件热更新哦~

如果需要调试源码,启动npm run dev即可,会开启tsc -w --p监听src目录的改动并实时输出到dist目录下,接下来就可以开启欢乐的源码时间~

入口文件


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

从入口bin/vite.js开始

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

可以看见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是通过下面这种middleware的形式注册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)

moduleResolverMiddleware


这个中间件的作用编译index.htmlSFC等文件内容,处理相关的依赖。

比如上面的html文件script标签内容,通过rewriteImports等方法的处理会被编译成

import { createApp } from /__modules/vue // 之前是import { createApp } from vue

import Comp from ./Comp.vue

createApp(Comp).mount( #app )

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

  • /__modules/vue是koa服务器的静态资源目录文件,

  • ./Comp.vue是我们编写的单页面组件文件

  • 此外貌似还会提供sourcemap等功能

对于入口文件而言,需要script标签下相关依赖。对于单页面组件而言,在vue-loader中,也需要处理tmplate、scriptstyle标签;在vite中,这些依赖都会被当做cssjs`文件请求的方式进行加载。

单页面组件主要包含templatescriptstyle标签,其中script标签内代码的导出会被编译成

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

import “/__hmrClient”

let __script; export default (__script = {

data: () => ({ count: 0 })

})

// 根据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”

styletemplate标签会被重写成/Comp.vue?type=xxx的形式,重新发送http请求,这个通过query参数的形式区分并加载SFC文件各个模块内容的方式,与vue-loader中通过webpackresourceQuery配置进行处理如出一辙,如果了解vue-loader运行原理的同学看到这里估计就已经恍然大悟了,之前写过一篇从vue-loader源码分析CSS-Scoped的实现,里面也介绍了vue-loader的大致原理。

回到vite,现在我们清楚了moduleResolverMiddleware的作用,主要就是重写模块路径,将SFC文件的依赖通过query参数进行区分,方便浏览器通过url加载实际模块。打开浏览器控制台,可以查看具体的文件请求

VuePlugin


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

单页面文件的请求有个特点,都是以*.vue作为请求路径结尾,当服务器接收到这种特点的http请求,主要处理

  • 根据ctx.path确定请求具体的vue文件

  • 使用parseSFC解析该文件,获得descriptor,一个descriptor包含了这个组件的基本信息,包括templatescriptstyles等属性下面是Comp.vue文件经过处理后获得的descriptor

{

filename: /Users/Txm/source_code/vite/examples/Comp.vue ,

template: {

type: template ,

content: <button @click=“count++”>{{ count }} times1,

loc: {

source: <button @click=“count++”>{{ count }} times1,

start: [Object],

end: [Object]

},

attrs: {},

map: {

version: 3,

sources: [Array],

names: [],

mappings: ;AACA ,

file: /Users/Txm/source_code/vite/examples/Comp.vue ,

sourceRoot: ,

sourcesContent: [Array]

}

},

script: {

type: script ,

content: export default {data: () => ({ count: 0 })},

loc: {

source: export default {data: () => ({ count: 0 })},

start: [Object],

end: [Object]

},

attrs: {},

ajax

1)ajax请求的原理/ 手写一个ajax请求?
2)readyState?
3)ajax异步与同步的区别?
4)ajax传递中文用什么方法?

ajax.PNG

前12.PNG

CA ,

file: /Users/Txm/source_code/vite/examples/Comp.vue ,

sourceRoot: ,

sourcesContent: [Array]

}

},

script: {

type: script ,

content: export default {data: () => ({ count: 0 })},

loc: {

source: export default {data: () => ({ count: 0 })},

start: [Object],

end: [Object]

},

attrs: {},

ajax

1)ajax请求的原理/ 手写一个ajax请求?
2)readyState?
3)ajax异步与同步的区别?
4)ajax传递中文用什么方法?

[外链图片转存中…(img-4U094KmW-1718097269318)]

[外链图片转存中…(img-fbGKzkJE-1718097269319)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值