iis服务器助手广告
返回顶部
首页 > 资讯 > 精选 >Vite的原理分析
  • 701
分享到

Vite的原理分析

2023-06-29 04:06:52 701人浏览 安东尼
摘要

这篇文章主要介绍了Vite的原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1. 概述Vite是一个更轻、更快的WEB应用开发工具,面向现代浏览器。底层基于ECMASc

这篇文章主要介绍了Vite的原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

1. 概述

Vite是一个更轻、更快的WEB应用开发工具,面向现代浏览器。底层基于ECMAScript标准原生模块系统ES Module实现。他的出现是为了解决webpack冷启动时间过长以及Webpack HMR热更新反应速度慢等问题。

默认情况下Vite创建的项目是一个普通的vue3应用,相比基于Vue-cli创建的应用少了很多配置文件和依赖。

Vite创建的项目所需要的开发依赖非常少,只有Vite@vue/compiler-sfc。这里面Vite是一个运行工具compiler-sfc则是为了编译.vue结尾的单文件组件。在创建项目的时候通过指定不同的模板也可以支持使用其他框架例如React。项目创建完成之后可以通过两个命令启动和打包。

# 开启服务器vite serve# 打包vite build

正是因为Vite启动的web服务不需要编译打包,所以启动的速度特别快,调试阶段大部分运行的代码都是你在编辑器中书写的代码,这相比于webpack的编译后再呈现确实要快很多。当然生产环境还是需要打包的,毕竟很多时候我们使用的最新ES规范在浏览器中还没有被支持,Vite的打包过程和webpack类似会将所有文件进行编译打包到一起。对于代码切割的需求Vite采用的是原生的动态导入来实现的,所以打包结果只能支持现代浏览器,如果需要兼容老版本浏览器可以引入Polyfill

使用Webpack打包除了因为浏览器环境并不支持模块化和新语法外,还有就是模块文件会产生大量的Http请求。如果你使用模块化的方式开发,一个页面就会有十几甚至几十个模块,而且很多时候会出现几kb的文件,打开一个页面要加载几十个js资源这显然是不合理的。

  • Vite创建的项目几乎不需要额外的配置默认已经支持TS、Less, Sass,Stylus,postCSS了,但是需要单独安装对应的编译器,同时默认还支持jsx和Web Assembly。

  • Vite带来的好处是提升开发者在开发过程中的体验,web开发服务器不需要等待即可立即启动,模块热更新几乎是实时的,所需的文件会按需编译,避免编译用不到的文件。并且开箱即用避免loader及plugins的配置。

  • Vite的核心功能包括开启一个静态的web服务器,能够编译单文件组件并且提供HMR功能。当启动vite的时候首先会将当前项目目录作为静态服务器的根目录,静态服务器会拦截部分请求,当请求单文件的时候会实时编译,以及处理其他浏览器不能识别的模块,通过websocket实现hmr。

2. 实现静态测试服务器

首先实现一个能够开启静态web服务器的命令行工具。vite1.x内部使用的是Koa来实现静态服务器。(ps:node命令行工具可以查看我之前的文章,这里就不介绍了,直接贴代码)。

npm initnpm install koa koa-send -D

工具bin的入口文件设置为本地的index.js

#!/usr/bin/env nodeconst Koa = require('koa')const send = require('koa-send')const app = new Koa()// 开启静态文件服务器app.use(async (ctx, next) => {    // 加载静态文件    await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html'})    await next()})app.listen(5000)console.log('服务器已经启动 http://localhost:5000')

这样就编写好了一个node静态服务器的工具。

3. 处理第三方模块

我们的做法是当代码中使用了第三方模块(node_modules中的文件),可以通过修改第三方模块的路径给他一个标识,然后在服务器中拿到这个标识来处理这个模块。

首先需要修改第三方模块的路径,这里需要一个新的中间件来实现。判断一下当前返回给浏览器的文件是否是javascript,只需要看响应头中的content-type。如果是javascript需要找到这个文件中引入的模块路径。ctx.body就是返回给浏览器的内容文件。这里的数据是一个stream,需要转换成字符串来处理。

const stream2string = (stream) => {    return new Promise((resolve, reject) => {        const chunks = [];        stream.on('data', chunk => {chunks.push(chunk)})        stream.on('end', () => { resolve(Buffer.concat(chunks).toString('utf-8'))})        stream.on('error', reject)    })}// 修改第三方模块路径app.use(async (ctx, next) => {    if (ctx.type === 'application/javascript') {        const contents = await stream2string(ctx.body);        // 将body中导入的路径修改一下,重新赋值给body返回给浏览器        // import vue from 'vue', 匹配到from '修改为from '@modules/        ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/');    }})

接着开始加载第三方模块, 这里同样需要一个中间件,判断请求路径是否是修改过的@module开头,如果是的话就去node_modules里面加载对应的模块返回给浏览器。这个中间件要放在静态服务器之前。

// 加载第三方模块app.use(async (ctx, next) => {    if (ctx.path.startsWith('/@modules/')) {        // 截取模块名称        const moduleName = ctx.path.substr(10);    }})

拿到模块名称之后需要获取模块的入口文件,这里要获取的是ES Module模块的入口文件,需要先找到这个模块的package.JSON然后再获取这个package.json中的module字段的值也就是入口文件。

// 找到模块路径const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');const pkg = require(pkgPath);// 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的ctx.path = path.join('/node_modules', moduleName, pkg.module);// 执行下一个中间件awiat next();

这样浏览器请求进来的时候虽然是@modules路径,但是在加载之前将path路径修改为了node_modules中的路径,这样在加载的时候就会去node_modules中获取文件,将加载的内容响应给浏览器。

加载第三方模块:

app.use(async (ctx, next) => {    if (ctx.path.startsWith('/@modules/')) {        // 截取模块名称        const moduleName = ctx.path.substr(10);        // 找到模块路径        const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');        const pkg = require(pkgPath);        // 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的        ctx.path = path.join('/node_modules', moduleName, pkg.module);        // 执行下一个中间件        awiat next();    }})

4. 单文件组件处理

之前说过浏览器是没办法处理.vue资源的, 浏览器只能识别js、css等常用资源,所以其他类型的资源都需要在服务端处理。当请求单文件组件的时候需要在服务器将单文件组件编译成js模块返回给浏览器。

所以这里当浏览器第一次请求App.vue的时候,服务器会把单文件组件编译成一个对象,先加载这个组件,然后再创建一个对象。

import Hello from './src/components/Hello.vue'const __script = {    name: "App",    components: {        Hello    }}

接着再去加载入口文件,这次会告诉服务器编译一下这个单文件组件的模板,返回一个render函数。然后将render函数挂载到刚创建的组件选项对象上,最后导出选项对象。

import { render as __render } from '/src/App.vue?type=template'__script.render = __render__script.__hmrId = '/src/App.vue'export default __script

也就是说vite会发送两次请求,第一次请求会编译单文件文件,第二次请求是编译单文件模板返回一个render函数。

编译单文件选项:

首先来实现一下第一次请求单文件的情况。需要把单文件组件编译成一个选项,这里同样用一个中间件来实现。这个功能要在处理静态服务器之后,处理第三方模块路径之前。

首先需要对单文件组件进行编译需要借助compiler-sfc

// 处理单文件组件app.use(async (ctx, next) => {    if (ctx.path.endsWith('.vue')) {        // 获取响应文件内容,转换成字符串        const contents = await streamToString(ctx.body);        // 编译文件内容        const { descriptor } = compilerSFC.parse(contents);        // 定义状态码        let code;        // 不存在type就是第一次请求        if (!ctx.query.type) {            code = descriptor.script.content;            // 这里的code格式是, 需要改造成我们前面贴出来的vite中的样子            // import Hello from './components/Hello.vue'            // export default {            //      name: 'App',            //      components: {            //          Hello            //      }            //  }            // 改造code的格式,将export default 替换为const __script =            code = code.relace(/export\s+default\s+/g, 'const __script = ')            code += `                import { render as __render } from '${ctx.path}?type=template'                __script.rener = __render                export default __script            `        }        // 设置浏览器响应头为js        ctx.type = 'application/javascript'        // 将字符串转换成数据流传给下一个中间件。        ctx.body = stringToStream(code);    }    await next()})const stringToStream = text => {    const stream = new Readable();    stream.push(text);    stream.push(null);    return stream;}
npm install @vue/compiler-sfc -D

接着我们再来处理单文件组件的第二次请求,第二次请求url会带上type=template参数,需要将单文件组件模板编译成render函数。

首先需要判断当前请求中有没有type=template

if (!ctx.query.type) {    ...} else if (ctx.query.type === 'template') {    // 获取编译后的对象 code就是render函数    const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })    // 将render函数赋值给code返回给浏览器    code = templateRender.code}

这里还要处理一下工具中的process.env,因为这些代码会返回到浏览器中运行,如果不处理会默认为node导致运行失败。可以在修改第三方模块路径的中间件中修改,修改完路径之后再添加一条修改process.env。

// 修改第三方模块路径app.use(async (ctx, next) => {    if (ctx.type === 'application/javascript') {        const contents = await stream2string(ctx.body);        // 将body中导入的路径修改一下,重新赋值给body返回给浏览器        // import vue from 'vue', 匹配到from '修改为from '@modules/        ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/').replace(/process\.env\.NODE_ENV/g, '"development"');    }})

至此就实现了一个简版的vite,当然这里我们只演示了.vue文件,对于css,less等其他资源都没有处理,不过方法都是类似的,感兴趣的同学可以自行实现。

#!/usr/bin/env nodeconst path = require('path')const { Readable } = require('stream)const Koa = require('koa')const send = require('koa-send')const compilerSFC = require('@vue/compiler-sfc')const app = new Koa()const stream2string = (stream) => {    return new Promise((resolve, reject) => {        const chunks = [];        stream.on('data', chunk => {chunks.push(chunk)})        stream.on('end', () => { resolve(Buffer.concat(chunks).toString('utf-8'))})        stream.on('error', reject)    })}const stringToStream = text => {    const stream = new Readable();    stream.push(text);    stream.push(null);    return stream;}// 加载第三方模块app.use(async (ctx, next) => {    if (ctx.path.startsWith('/@modules/')) {        // 截取模块名称        const moduleName = ctx.path.substr(10);        // 找到模块路径        const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');        const pkg = require(pkgPath);        // 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的        ctx.path = path.join('/node_modules', moduleName, pkg.module);        // 执行下一个中间件        awiat next();    }})// 开启静态文件服务器app.use(async (ctx, next) => {    // 加载静态文件    await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html'})    await next()})// 处理单文件组件app.use(async (ctx, next) => {    if (ctx.path.endsWith('.vue')) {        // 获取响应文件内容,转换成字符串        const contents = await streamToString(ctx.body);        // 编译文件内容        const { descriptor } = compilerSFC.parse(contents);        // 定义状态码        let code;        // 不存在type就是第一次请求        if (!ctx.query.type) {            code = descriptor.script.content;            // 这里的code格式是, 需要改造成我们前面贴出来的vite中的样子            // import Hello from './components/Hello.vue'            // export default {            //      name: 'App',            //      components: {            //          Hello            //      }            //  }            // 改造code的格式,将export default 替换为const __script =            code = code.relace(/export\s+default\s+/g, 'const __script = ')            code += `                import { render as __render } from '${ctx.path}?type=template'                __script.rener = __render                export default __script            `        } else if (ctx.query.type === 'template') {            // 获取编译后的对象 code就是render函数            const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })            // 将render函数赋值给code返回给浏览器            code = templateRender.code        }        // 设置浏览器响应头为js        ctx.type = 'application/javascript'        // 将字符串转换成数据流传给下一个中间件。        ctx.body = stringToStream(code);    }    await next()})// 修改第三方模块路径app.use(async (ctx, next) => {    if (ctx.type === 'application/javascript') {        const contents = await stream2string(ctx.body);        // 将body中导入的路径修改一下,重新赋值给body返回给浏览器        // import vue from 'vue', 匹配到from '修改为from '@modules/        ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/').replace(/process\.env\.NODE_ENV/g, '"development"');    }})app.listen(5000)console.log('服务器已经启动 http://localhost:5000')

感谢你能够认真阅读完这篇文章,希望小编分享的“Vite的原理分析”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网精选频道,更多相关知识等着你来学习!

--结束END--

本文标题: Vite的原理分析

本文链接: https://www.lsjlt.com/news/322508.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Vite的原理分析
    这篇文章主要介绍了Vite的原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1. 概述Vite是一个更轻、更快的web应用开发工具,面向现代浏览器。底层基于ECMASc...
    99+
    2023-06-29
  • 学习Vite的原理
    目录1. 概述2. 实现静态测试服务器3. 处理第三方模块4. 单文件组件处理1. 概述 Vite是一个更轻、更快的web应用开发工具,面向现代浏览器。底层基于ECMAScript标...
    99+
    2024-04-02
  • Vue3和Vite实例分析
    今天小编给大家分享一下Vue3和Vite实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.创建一个vite项目npm...
    99+
    2023-06-29
  • bootstrap的原理分析
    这篇文章主要介绍了bootstrap的原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。网格系统的实现原理,是通过定义容器大小,平分12...
    99+
    2024-04-02
  • CSS原理分析
    这篇“CSS原理分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“CSS原理分析”文章吧。...
    99+
    2024-04-02
  • HTTPS原理分析
    这篇文章主要介绍“HTTPS原理分析”,在日常操作中,相信很多人在HTTPS原理分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HTTPS原理分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!HTTPS...
    99+
    2023-06-17
  • 分析JDK的HashMap的原理
    这篇文章主要介绍“分析JDK的HashMap的原理”,在日常操作中,相信很多人在分析JDK的HashMap的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分析JDK的HashMap的原理”的疑惑有所帮助!...
    99+
    2023-06-02
  • 深入剖析vite到底是快还是慢原理详解
    目录前言Vite 的快快速的冷启动快速的热更新Vite 的慢首屏性能懒加载性能结束语前言 谈到 Vite,给人的第一印象就是 dev server 启动速度快。同样规模的项目,相比 ...
    99+
    2022-11-13
    vite快慢剖析 vite快慢
  • DHCP的工作原理分析
    这篇文章主要介绍了DHCP的工作原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。DHCP动态主机配置协议:计算机用来获得配置信息的协议。DHCP容许给某一计算机赋以IP...
    99+
    2023-06-27
  • jsonp原理的示例分析
    小编给大家分享一下jsonp原理的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一:跨域问题。二,跨域产生的原因Js是不...
    99+
    2024-04-02
  • AJAX原理的示例分析
    这篇文章将为大家详细讲解有关AJAX原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。先上原理图: 背景:   &nbs...
    99+
    2024-04-02
  • docker容器的原理分析
    目录01 容器的本质是什么?02 Cgroup技术和Namespace技术介绍03 容器、镜像和仓库之间的关系01 容器的本质是什么?    ...
    99+
    2024-04-02
  • js中Proxy的原理分析
    这篇文章给大家分享的是有关js中Proxy的原理分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。什么是代理模式引入一个现实生活中的案例我们作为用户需要去如何评估一个房子的好坏、如何办理住房手续等一些列繁琐的事物...
    99+
    2023-06-15
  • Android ANR原理分析
    目录卡顿原理 卡顿监控 ANR原理 卡顿原理 主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,Zygote会反射调用ActivityThread的main...
    99+
    2024-04-02
  • springboot中docker的原理分析
    这篇文章主要介绍“springboot中docker的原理分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“springboot中docker的原理分析”文章能帮助大家解决问题。一、添加maven配...
    99+
    2023-06-08
  • python中GIL的原理分析
    小编给大家分享一下python中GIL的原理分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!python是什么意思Python是一种跨平台的、具有解释性、编译性...
    99+
    2023-06-14
  • java中HashMap的原理分析
    这篇文章主要介绍java中HashMap的原理分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语言的代表,实现了面向对象理论,允许程序员以...
    99+
    2023-06-14
  • JavaScript中Debugger的原理分析
    这篇文章给大家分享的是有关JavaScript中Debugger的原理分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。代码运行的原理是什么代码的运行方式可以分为直接执行和解释执...
    99+
    2024-04-02
  • vite项目构建优化的示例分析
    这篇文章主要介绍了vite项目构建优化的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。vite项目构建优化路由动态导入 经过rollup的构建,动态导入的文件将会生成...
    99+
    2023-06-20
  • React中State的原理分析
    这篇文章主要介绍了React中State的原理分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。问题:setState 到底是同步还是异步的?如果对 React 底层有一定了...
    99+
    2023-06-26
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作