Vite的原理介绍及应用

背景

在当今Webpack横行的时代,Webpack的影响力不可谓之不大。对于一个主流Web项目的开发而言,大多数时候我们都会采用现有的脚手架作为项目开发,打包工具如:Vue-cli、create-react-app,而他们都基于Webpack。但是,在不断的使用和日常项目的迭代中,我们慢慢会走入一个窘境,就会出现我们稍微改动一行代码我们就需要等待十几秒甚至是数十秒的情况,这对于我们日益增长的业务开发来说是十分不友好的。
深入Webpack打包原理我们可以清晰的知道他的编译过程是静态的,也就是说他会把所有可能用到的代码全部进行打包构建,会借助胶水代码用来组装各模块,这样打包出来的代码是十分庞大的,很多时候其实我们在开发过程中并不需要全部代码的功能,而是一小部分,这个时候大量的构建时间都是多余的,我们需要一个能够真正意义上实现懒加载的开发工具。

Vite是什么?

定义

Vite(轻量,轻快)vite是一个基于vue3单文件组件的非打包开发服务器。它做到了本地快速开发启动,实现按需编译,不再等待整个应用编译完成。
面向现代化浏览器,基于原生模块系统 ES moudle 的开发服务器,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。
浏览器端使用 export、import 的方式导入和导出模块,在 script 标签里设置 type="module",浏览器会识别所有添加了type='module'的script标签,对于该标签中的import关键字,浏览器会发起http请求获取模块内容。

它主要具有以下特点:

  • 快速的冷启动
  • 即时的模块热更新
  • 真正的按需编译

基本架构

启动项目时,则是启动一个koa服务器,服务器拦截浏览器的es module的请求,服务器直接将 ESM 模块内容处理后,通过 path 找到目录下对应的文件做一定的处理最终以 ES Modules 格式返回给客户端。接着,现代浏览器通过解析 script module,对每一个 import 到的模块进行 HTTP 请求,服务器继续对这些 HTTP 请求进行处理并响应。(热更新)


image.png

图中的目标项目即我们开发时的项目,vite服务在解析模块路径以及读取文件内容时需要访问目标项目中的模块内容或者配置文件等。

Vite的原理介绍

  • 使用vite后项目分析
它的核心在于使用了es的语法,我们可以看到index.html文件,和以前不一样的地方在于,它使用了:
  <script type="module" src="/src/main.js"></script>
// 这样引入的话就表示它是一个模块,那这个script里面就可以使用import
//默认引入的是src下的main.js
image.png

从其他请求中我们也可以看出每一个.vue文件都被拆分成了多个请求,并通过type来标识是template还是style。


image.png
  • 结论

vite在这里做了两件事,第一是修改了模块请求路径,第二就是将.vue文件进行解析拆分。(只是本文会进行详细讲解的有关于Vite实现的部分,而不是说Vite只干了这两件事 Vite的功能还是十分强大的)

深入源码讲原理

1、创建自己的vite工具 my-vite

项目目录概述:

  • my-vite
    -bin
    www.js
    -node_modules
    -plugins
    -index.js
    -package.json

在项目根目录创建bin目录,并在bin目录下创建一个www.js文件,文件内容如下:

! /usr/bin/env node
// 执行的入口
require('../index');

2、创建服务

在创建www.js文件中引入了index.js文件,主要是使用了koa启动了一个简单服务以及实现vite的功能

const Koa = require('koa')
const { moduleRewritePlugin } = require('./plugins/serverPluginModuleRewrite')
const serveStaticPlugin = require('./plugins/serverPluginServeStatic')
const { moduleResolvePlugin } = require('./plugins/serverPluginModuleResolve')
const { vuePlugin } = require('./plugins/serverPluginVue')

function createServer() {
    let app = new Koa();
    const context = { //直接创建一个上下文,来给不同的插件共享功能
        app,
        root: process.cwd()     
     }
    const resolvePlugin = [
        moduleRewritePlugin, //2.重写我们的请求路径, 重写后浏览器会再次发送请求
        moduleResolvePlugin,//解析模块路径,服务端来解析模块真实位置
        vuePlugin, // 解析.vue文件
        serveStaticPlugin //1.静态服务插件,处理所需要处理的静态文件    
    ]
    resolvePlugin.forEach(plugin => plugin(context))
    return app;
}
createServer().listen(4000, () => {
    console.log('vite start 4000')
})

3、静态服务插件

serverPluginServeStatic.js文件:使用koa-static中间件来托管静态资源,同时我们需要拿到koa实例(app),其次需要获取到目标项目的根目录路径(root),将目标项目进行整体托管,同时对于目标项目的 public目录也进行托管,这样,我们需要处理的静态文件基本完成了。

const static = require('koa-static')
const path = require('path')

function serveStaticPlugin({ app, root }) {

    app.use(static(root)) // root指的是根目录 访问根目录下的index.html
    app.use(static(path.resolve(root, 'public'))) //可以直接http://localhost:4000/文件名,访问public下的文件,比如public文件下有aa.txt,则直接访问http://localhost:4000/aa.txt
}

module.exports = serveStaticPlugin

4、重写模块路径插件

为什么要重写模块路径呢?
这是因为我们在使用import方式导入模块的时候,浏览器只能识别./、../、/这种开头的路径,对于直接使用模块名比如:import vue from 'vue',浏览器就会报错,因为它无法识别这种路径,这就是我们需要进行处理的地方了。

image.png

serverPluginModuleRewrite.js文件:

image.png

image.png

5、解析模块路径插件

在处理完所有的模块路径之后,我们就需要在服务端来解析模块真实位置。

const reg = /^\/@modules\//
const fs = require('fs').promises
const path = require('path')
 
function moduleResolvePlugin({ app, root }) {
    app.use(async(ctx, next) => {
        // 如果没有匹配到 /@modules就往下执行即可
        if (!reg.test(ctx.path)) {
            return next();
        }
        const id = ctx.path.replace(reg, '');
        //console.log("id-->", id)  //vue
        let mapping = {
                vue: path.resolve(root, 'node_modules', '@vue/runtime-dom/dist/runtime-dom.esm-browser.js')
            }
            //如果是第三方模块,则可以根据package.json进行查找
        const content = await fs.readFile(mapping[id], 'utf8')
        ctx.type = 'js' //返回的文件为js
        ctx.body = content
    })
}

exports.moduleResolvePlugin =  moduleResolvePlugin

6、解析.vue文件

当 Vite 遇到一个 .vue 后缀的文件时。由于 .vue 模板文件的特殊性,它被拆分成 template , css ,script 模块三个模块进行分别处理。最后会对 script ,template, css 发送多个请求获取。


image.png

7、总结

在 koa 中间件里获取请求 body
通过 es-module-lexer 解析资源 ast 拿到 import 的内容
判断 import 的资源是否是 npm 模块
返回处理后的资源路径:"vue" => "/@modules/vue"
将处理的template,script,style等所需的依赖以http请求的形式,通过query参数形式区分并加载SFC文件各个模块内容。

Webpack & Vite 对比

  • 冷启动对比


    image.png

    从左到右依次是: vue-cli3 + vue3 的demo, vite 1.0.0-rc + vue 3的demo, vue-cli3 + vue2的demo。在这个 gif 中已经可以明显感受到 vite 的优势了。vue-cli3 启动Vue2大概需要5s左右,vue-cli3 启动Vue3需要4s左右,而vite 只需要1s 左右的时间。

1.当我们对比使用 vue-cli-service serve 的时候,会有明显感觉。因为 Webpack Dev Server 在启动时,需要先 build—遍,而 build 的过程是需要耗费很多时间的。
2.而 Vite 则完全不同,当我们执行 Vite serve 时(npm run dev),内部直接启动了 Web Server,并不会先编译所有的代码文件。那仅仅是启动 Web Server,那么及时请求的编译呢?关于支持 JSX, TSX,Typescript 编译到原生 JS —— Vite 引入了EsBuild,是使用 Go 写的,直接编译为 Native 代码,性能要比 TSC 好二三十倍, 当然也会用上缓存。

  • 即时的热模块更新

1.热更新的时候,Vite 只需要立即编译当前所修改的文件即可,所以 响应速度非常快。
2.而 Webpack 修改某个文件过后,会自动以这个文件为入口重写 build—次,所有的涉及到的依赖也都会被加载一遍,所以反应速度会慢很多。

  • 真正的按需编译

1.Webpack 这类工具的做法是将所有模块提前编译、打包进 bundle 里,换句话说,不管模块是否会被执行,都要被编译和打包到 bundle 里。随着项目越来越大打包后的 bundle 也越来越大,打包的速度自然也就越来越慢。
2.Vite 利用现代浏览器原生支持 ESM 特性,省略了对模块的打包。对于需要编译的文件,Vite 采用的是另外一种模式:即时编译。也就是说,只有具体去请求某个文件时才会编译这个文件。所以,这种「即时编译」的好处主要体现在:按需编译。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容