前端构建新玩法 Vite 上手与思考

news2024/12/26 14:42:16

# Vite 的定义

Vite 是面向现代浏览器的一个更轻、更快的 Web 应用开发工具,核心基于 ECMAScript 标准原生模块系统(ES Modules)实现。

表象功能上看,Vite 可以取代基于 Webpack 的 vue-cli 或者 cra 的集成式开发工具,提供全新的一种开发体验。

具体细节往下看。

# Vite 的由来

在此之前,如果我们所开发的应用比较复杂(代码量偏大),使用 Webpack 的开发过程相对没有那么「丝滑」,具体表现为以下两点:

  • Webpack Dev Server 冷启动时间会比较长,稍大一点的项目启动开发服务都需要等待 10 - 20 秒;
  • Webpack HMR 热更新的反应速度比较慢,修改完代码需要等待编译器全部编译完成才能开始同步到浏览器;

# 快速上手

这里我们话不多说,先上手体验一下 Vite,然后再来分析其内部的思路和想法。

Vite 官方目前提供了一个比较简单的脚手架:create-vite-app,可以使用这个脚手架快速创建一个使用 Vite 构建的 Vue.js 应用

$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

   
   

如果使用 Yarn:

$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev

   
   

P.S. npm init 或者 yarn create 是这两个包管理工具提供的新功能, 其内部就是自动去安装一个 create-<xxx> 的模块(临时),然后自动执行这个模块中的 bin。 例如: yarn create react-app my-react-app 就相当于先 yarn global add create-react-app,然后自动执行 create-react-app my-react-app

# 对比差异点

打开生成的项目过后,你会发现就是一个很普通的 Vue.js 应用,没有太多特殊的地方。

不过相比于之前 vue-cli 创建的项目或者是基于 Webpack 搭建的 Vue.js 项目,这里的开发依赖非常简单,只有 vite@vue/compiler-sfc

{
  "name": "vite-demo",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.0.0-rc.1"
  },
  "devDependencies": {
    "vite": "^1.0.0-rc.1",
    "@vue/compiler-sfc": "^3.0.0-rc.1"
  }
}

   
   

vite 就是我们今天要介绍的主角,而 @vue/compiler-sfc 就是用来编译我们项目中 .vue 结尾的单文件组件(SFC),它取代的就是 Vue.js 2.x 时使用的 vue-template-compiler

再者就是 Vue.js 的版本是 3.0。这里尤其需要注意:Vite 目前只支持 Vue.js 3.0 版本。

如果你想,在后面介绍完实现原理过后,你也可以改造 Vite 让它支持 Vue.js 2.0。

# 基础体验

这里我们所安装的 vite 模块提供了两个子命令:

  • serve:启动一个用于开发的服务器
  • build:构建整个项目(上线)

当我们执行 vite serve 的时候,你会发现响应速度非常快,几乎就是秒开。

可能单独体验你不会有太明显的感觉,你可以对比使用 vue-cli-service(内部还是 Webpack)启动开发服务器,

当我们对比使用 vue-cli-service serve 的时候,你会有更明显的感觉。

因为 Webpack Dev Server 在启动时,需要先 build 一遍,而 build 的过程是需要耗费很多时间的。

而 Vite 则完全不同,当我们执行 vite serve 时,内部直接启动了 Web Server,并不会先编译所有的代码文件。

那仅仅是启动 Web Server,速度上自然就快了很多。

但是像 Webpack 这类工具的做法是将所有模块提前编译、打包进 bundle 里,换句话说,不管模块是否会被执行,都要被编译和打包到 bundle 里。随着项目越来越大打包后的 bundle 也越来越大,打包的速度自然也就越来越慢。

Vite 利用现代浏览器原生支持 ESM 特性,省略了对模块的打包。

对于需要编译的文件,Vite 采用的是另外一种模式:即时编译。

也就是说,只有具体去请求某个文件时才会编译这个文件。

所以,这种「即时编译」的好处主要体现在:按需编译。

# Optimize

Vite 还提供了一个目前在帮助列表中并没有呈现的一个子命令:optimize。

这个命令的作用就是单独的去「优化依赖」。

所谓的「优化依赖」,指的就是自动去把代码中依赖的第三方模块提前编译出来。

例如,我们在代码中通过 import 载入了 vue 这个模块,那通过这个命令就会自动将这个模块打包成一个单独的 ESM bundle, 放到 node_modules/.vite_opt_cache 目录中。

这样后续请求这个文件时就不需要再即时去加载了。

# HMR

同样也是模式的问题,热更新的时候,Vite 只需要立即编译当前所修改的文件即可,所以响应速度非常快。

而 Webpack 修改某个文件过后,会自动以这个文件为入口重写 build 一次,所有的涉及到的依赖也都会被加载一遍,所以反应速度会慢很多。

# Build

Vite 在生产模式下打包,需要使用 vite build 命令。

这个命令内部采用的是 Rollup 完成的应用打包,最终还是会把文件都提前编译并打包到一起。

对于 Code Splitting 需求,Vite 内部采用的就是原生 Dynamic imports 特性实现的,所以打包结果还是只能够支持现代浏览器。

不过好在 Dynamic imports 特性是可以有 Polyfill 的:https://github.com/GoogleChromeLabs/dynamic-import-polyfill ,也就是说,只要你想,它也可以运行在相对低版本的浏览器中。

# 打包 or 不打包

Vite 的出现,引发了另外一个值得我们思考的问题:究竟还有没有必要打包应用?

之前我们使用 Webpack 打包应用代码,使之成为一个 bundle.js,主要有两个原因:

  1. 浏览器环境并不支持模块化
  2. 零散的模块文件会产生大量的 HTTP 请求

随着浏览器的对 ES 标准支持的逐渐完善,第一个问题已经慢慢不存在了。现阶段绝大多数浏览器都是支持 ES Modules 的。

零散模块文件确实会产生大量的 HTTP 请求,而大量的 HTTP 请求在浏览器端就会并发请求资源的问题;

如上图所示,红色圈出来的请求就是并行请求,但是后面的请求就因为域名链接数已超过限制,而被挂起等待了一段时间。

在 HTTP 1.1 的标准下,每次请求都需要单独建立 TCP 链接,经过完整的通讯过程,非常耗时;

而且每次请求除了请求体中的内容,请求头中也会包含很多数据,大量请求的情况下也会浪费很多资源。

但是这些问题随着 HTTP 2 的出现,也就不复存在了。

关于 HTTP 1.1 与 HTTP 2 之间的差异,可以通过这个链接体验:https://http2.akamai.com/demo ,直观感受下 HTTP/2 比 HTTP/1 到底快了多少。

而且不打包也有一个好处,就是可以把按需加载实现到极致。

关于 HTTP 2 的详细介绍,可以参考:

  • https://blog.fundebug.com/2019/03/07/understand-http2-and-http3/
  • https://www.digitalocean.com/community/tutorials/http-1-1-vs-http-2-what-s-the-difference

# 开箱即用

  • TypeScript - 内置支持
  • less/sass/stylus/postcss - 内置支持(需要单独安装所对应的编译器)

# 特性小结

Vite 带来的优势主要体现在提升开发者在开发过程中的体验。

  • Dev Server 无需等待,即时启动;
  • 几乎实时的模块热更新;
  • 所需文件按需编译,避免编译用不到的文件;
  • 开箱即用,避免各种 Loader 和 Plugin 的配置;

# 实现原理

Vite 的核心功能:Static Server + Compile + HMR

核心思路:

  1. 将当前项目目录作为静态文件服务器的根目录
  2. 拦截部分文件请求
    1. 处理代码中 import node_modules 中的模块
    2. 处理 vue 单文件组件(SFC)的编译
  3. 通过 WebSocket 实现 HMR

# 手写实现

详细参考 https://github.com/zce/vite-essentials

#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream')
const Koa = require('koa')
const send = require('koa-send')
const compilerSfc = require('@vue/compiler-sfc')
const cwd = process.cwd()
const streamToString = stream =>
  new Promise((resolve, reject) => {
    const chunks = []
    stream.on('data', chunk => chunks.push(chunk))
    stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
    stream.on('error', reject)
  })
const app = new Koa()
// 重写请求路径,/@modules/xxx => /node_modules/
app.use(async (ctx, next) => {
  if (ctx.path.startsWith('/@modules/')) {
    const moduleName = ctx.path.substr(10) // => vue
    const modulePkg = require(path.join(cwd, 'node_modules', moduleName, 'package.json'))
    ctx.path = path.join('/node_modules', moduleName, modulePkg.module)
  }
  await next()
})
// 根据请求路径得到相应文件 /index.html
app.use(async (ctx, next) => {
  // ctx.path // http://localhost:3080/
  // ctx.body = 'my-vite'
  await send(ctx, ctx.path, { root: cwd, index: 'index.html' }) // 有可能还需要额外处理相应结果
  await next()
})
// .vue 文件请求的处理,即时编译
app.use(async (ctx, next) => {
  if (ctx.path.endsWith('.vue')) {
    const contents = await streamToString(ctx.body)
    const { descriptor } = compilerSfc.parse(contents)
    let code
    if (ctx.query.type === undefined) {
      code = descriptor.script.content
      code = code.replace(/export\s+default\s+/, 'const __script = ')
      code += `
  import { render as __render } from "${ctx.path}?type=template"
  __script.render = __render
  export default __script`
      // console.log(code)
      ctx.type = 'application/javascript'
      ctx.body = Readable.from(Buffer.from(code))
    } else if (ctx.query.type === 'template') {
      const templateRender = compilerSfc.compileTemplate({
        source: descriptor.template.content
      })
      code = templateRender.code
    }
    ctx.type = 'application/javascript'
    ctx.body = Readable.from(Buffer.from(code))
  }
  await next()
})
// 替换代码中特殊位置
app.use(async (ctx, next) => {
  if (ctx.type === 'application/javascript') {
    const contents = await streamToString(ctx.body)
    ctx.body = contents
      .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"production"')
  }
})
app.listen(3080)
console.log('Server running @ http://localhost:3080')

    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1798267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MAX7219(模拟SPI)驱动灯环的简单应用

文章目录 一、MAX7219是什么&#xff1f;二、使用步骤1.硬件1.1 引脚说明1.2 应用电路1.2.1 驱动数码管1.2.2 驱动点阵 2.软件2.1 时序2.2 寄存器2.2.1 掉电寄存器2.2.2 译码模式寄存器2.2.3 亮度寄存器2.2.4 扫描寄存器2.2.5 显示测试寄存器 2.3 初始化2.4 控制左侧灯环特定位…

支付平台界面感知评估

目标&#xff1a; 了解本地用户在本地语言下对产品用户界面 (UI) 的感受和体验&#xff1a; 界面的目的是否对本地用户清晰&#xff0c;并且是否符合本地文化和国家标准&#xff1b;界面中的文本是否正确显示&#xff0c;是否存在语法、拼写或其他错误&#xff0c;包括品牌一致…

Vue3【九】reactive 创建对象类型的响应式数据

Vue3【九】reactive 创建对象类型的响应式数据 reactive() 定义对象类型的响应式数据&#xff0c;可以任何类型的对象 默认开启对象的深度响应绑定 不能创建基本类型的响应式数据 案例截图 案例目录结构 案例代码 Person <template><div class"person">…

IC-Light:图像打光控制和背景融合生产力工具,最全ComfyUI操作指南

IC-Light&#xff1a;图像打光控制和背景融合ComfyUI操作指南 IC-Ligh简介 IC-Light是一款由Controlnet作者lllyasviel创作的最新作品&#xff0c;用于实现操控图像光照效果的项目。当前该项目已经发布了两种类型的模型&#xff1a;文本条件模型和背景条件模型。这两款模型都…

linux驱动学习(七)之混杂设备

需要板子一起学习的可以这里购买&#xff08;含资料&#xff09;&#xff1a;点击跳转 一、混杂设备 混杂设备也叫杂项设备&#xff0c;是对普通的字符设备(struct cdev)的一种封装,设计目的就是为了简化字符设备驱动设计的流程。具有以下特点&#xff1a; 1) 主设备号为10&a…

鸿蒙轻内核M核源码分析系列十七(2) 异常钩子函数的注册操作

本文中所涉及的源码&#xff0c;以OpenHarmony LiteOS-M内核为例&#xff0c;均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。鸿蒙轻内核异常钩子模块代码主要在components\exchook目录下。异常钩子函数的注册、解注册、异常钩子类型定义在utils\los_de…

【Spring框架全系列】SpringBoot_3种配置文件_yml语法_多环境开发配置_配置文件分类(详细)

文章目录 1.三种配置文件2. yaml语法2.1 yaml语法规则2.2 yaml数组数据2.3 yaml数据读取 3. 多环境开发配置3.1 多环境启动配置3.2 多环境启动命令格式3.3 多环境开发控制 4. 配置文件分类 1.三种配置文件 问题导入 框架常见的配置文件有哪几种形式&#xff1f; 比如&#xf…

Perfectly Clear WorkBench v4 解锁版安装教程 (图像修复增强工具)

前言 Perfectly Clear WorkBench 是一款图像修复工具&#xff0c;可以帮助用户对自己的图片素材进行修复&#xff0c;很多的照片因为拍摄问题&#xff0c;或者设备限制&#xff0c;会导致拍摄效果不好&#xff0c;使用这款软件可以进行一定程度的修复&#xff0c;当拍摄时亮度…

RK3588推理RetinaFace出现问题

RK3588推理RetinaFace出现问题 在RK3588上测试RockChip提供的RetinaFace模型时&#xff0c;出现下面的问题 $ python RetinaFace_pic.py done --> Init runtime environment I RKNN: [02:27:16.501] RKNN Runtime Information: librknnrt version: 1.5.2 (c6b7b351a2023…

图片转pdf在线网站,图片转pdf在线网址,工具软件

在现代办公和学习环境中&#xff0c;图片转PDF的操作已变得日益重要。无论是为了存档、分享还是打印&#xff0c;将图片转换为PDF格式都能带来诸多便利。本文将详细介绍几种常用的图片转PDF方法。 打开 “轻云pdf处理官网” &#xff0c;上传图片。 图片上传完成后&#xff0…

C++STL---stack queue知识汇总

前言 C将stack和queue划归到了Containers中&#xff0c;但严格的说这并不准确&#xff0c;stack和queue实际上已经不再是容器了&#xff0c;而是属于容器适配器&#xff0c;适配器做的功能是转换&#xff0c;即&#xff1a;它不是直接实现的&#xff0c;而是由其他容器封装转换…

SOLIDWORKS认证考试的目的

在当今日益发展的工程设计和制造领域&#xff0c;SOLIDWORKS作为一款功能强大的三维CAD设计软件&#xff0c;已经得到了广泛的认可和应用。为了评估和提升用户在使用SOLIDWORKS软件时的专业技能和能力&#xff0c;SOLIDWORKS公司推出了认证考试项目。本文将深入探讨SOLIDWORKS认…

Docker-compose安装、使用,容器化部署springboot项目

目录 一、docker-compose的安装 1、按官网的方式去安装 2、通过pip进行安装 3、离线安装 二、docker-compose常用命令 三、docker-compose.yml 说明 1.基本结构 四、docker-compose部署SpringBoot项目 1.编写docker-compose.yml文件 2.使用docker-compose启动容器…

eNSP学习——RIP与不连续子网

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、组建基本的RIPv1网络 3&#xff0e;RIPv1中解决不连续子网问题 4&#xff0e;RIPv2中解决不连续子网问题 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NS…

小程序 UI 风格魅力非凡

小程序 UI 风格魅力非凡

web安全基础学习笔记

这里写目录标题 1.使用hackbar2.php漏洞基本分析 弱类型语言2.2 php漏洞找到隐藏的源代码之 index.php~2.3 php漏洞找到隐藏的源代码之 vim的临时文件 /.index.php.swp3.php漏洞基本分析 数组 3.php漏洞基本分析 extract4.php漏洞基本分析 strpos eregi函数漏洞4.php漏洞基本分…

docker学习--最详细的docker run 各子命令解释与应用

文章目录 docker run应用docker run -it那怎样才能退出容器而不用容器关闭呢 docker run -d-p-P--name docker run 容器运行命令 docker run 常见的子命令及其含义 -i 交互式&#xff0c;和-t一起使用 -t 打开一个终端 -d 后台运行 -p/-P 暴露容器中的服务端口 –name 指定容…

如何备份和恢复华为手机?

智能手机已成为我们日常生活中不可或缺的一部分&#xff0c;它们存储着大量敏感数据。因此&#xff0c;确保数据安全&#xff0c;定期备份至关重要&#xff0c;以防手机意外丢失、损坏或被盗。 如果您拥有华为设备&#xff0c;并且正在寻找如何将华为手机备份到PC的方法&#…

接口签名和postman预处理生成签名

nestjs后端代码 controller Get(md5hmacSHA1b64)postMd5hmacSHA1b64(Req() request: Request, Query() query) {// 获取GET请求参数const queryParamsMap new Map(Object.entries(query));return this.handleMd5hmacSHA1b64(queryParamsMap, request);}Post(md5hmacSHA1b64)U…

【Redis】解决 Redis 运行在 Protected Mode 下的 DENIED 错误:消除 Redis 受保护模式的完美方案

【Redis】解决 Redis 运行在 Protected Mode 下的 DENIED 错误&#xff1a;消除 Redis 受保护模式的完美方案 大家好 我是寸铁&#x1f44a; 总结了一篇【Redis】解决 Redis 运行在 Protected Mode 下的 DENIED 错误&#xff1a;消除 Redis 受保护模式的完美方案✨ 喜欢的小伙伴…