大前端学习笔记 -- Vite实现原理

举报
楚楚冻人玥玥仙女 发表于 2021/11/19 01:21:28 2021/11/19
【摘要】 Vite实现原理 文章内容输出来源:大前端高薪训练营 一、Vite介绍 1. Vite概念 Vite是一个面向现代浏览器的一个更轻更快的web应用开发工具它基于ECMASc...

Vite实现原理

文章内容输出来源:大前端高薪训练营

一、Vite介绍

1. Vite概念

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

2. Vite项目依赖

  • Vite
  • @vue/compiler-sfc

3. 基础使用

vite serve / vite build
在这里插入图片描述

在执行vite serve的时候不需要打包,直接开启一个web服务器,当浏览器请求服务器,比如请求一个单文件组件,这个时候在服务器端编译单文件组件,然后把编译的结果返回给浏览器,注意这里的编译是在服务器端,另外模块的处理是在请求到服务器端处理的。

而执行vue-cli-service serve
在这里插入图片描述

当运行vue-cli-service serve的时候,它内部会使用webpack,首先去打包所有的模块,如果模块数量比较多的话,打包速度会非常的慢,把打包的结果存储到内存中,然后才会开启开发的web服务器,浏览器请求web服务器,把内存中打包的结果直接返回给浏览器,像webpack这种工具,它的做法是将所有的模块提前编译打包进bundle里,也就是不管模块是否被执行,是否使用到,都要被编译和打包到bundle。随着项目越来越大,打包后的bundle也越来越大,打包的速度自然也就越来越慢。

Vite利用现代浏览器原生支持的ESModule这个模块化的特性省略了对模块的打包,对于需要编译的文件,比如单文件组件、样式模块等,vite采用的另一种模式即时编译,也就是说只有具体去请求某个文件的时候,才会在服务端编译这个文件,所以这种即时编译的好处主要体现在按需编译,速度会更快。

4. HMR

  • Vite HMR
    • 立即编译当前所修改的文件
  • Webpack HMR
    • 会自动以这个文件位入口重新build一次,所有的涉及到的依赖也会被重新加载一次,所以反应速度会慢一些

5. Build

  • Vite build
    • Rollup
    • Dynamic import
      • polyfill

6. 打包OR不打包

  • 使用Webpack打包的两个原因:
    • 浏览器环境并不支持模块化(而现在大部分浏览器都支持ESM模块化了)
    • 零散的模块文件会产生大量的HTTP请求(HTTP2可以长连接)

7. 浏览器对ESModule的支持

现代化浏览器都支持ESModule模块化方式

8. 开箱即用

  • TypeScript - 内置支持
  • less/sass/stylus/postcss - 内置支持(需要单独安装)
  • JSX
  • Web Assembly

9. Vite特性

  • 快速冷启动
  • 模块热更新
  • 按需编译
  • 开箱即用

二、静态Web服务器

1. Vite核心功能

  • 静态web服务器
  • 编译单文件组件:拦截浏览器不识别的模块,并处理
  • HMR

三、修改第三方模块的路径

创建两个中间件,一个中间件是把加载第三方模块中的import中的路径改变,改成加载@modules/模块文件名,另一个中间件是当请求过来之后,判断请求路径中是否有@modules/模块名称,如果有的话,去node_modules加载对应的模块

四、加载第三方模块

当请求过来之后,判断请求路径中是否以@modules开头,如果是的话,去node_modules加载对应的模块

五、编译单文件组件

发送两次请求,第一次请求是把单文件组件编译成一个对象,第二次请求是编译单文件组件的模板,返回一个render函数,并且把render函数挂载到对象的render方法上。

最终代码:

#!/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 app = new Koa()

// 将流转化成字符串
const streamToString = stream => 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
}

// 3. 加载第三方模块。判断请求路径中是否以`@modules`开头,如果是的话,去node_modules加载对应的模块
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  if (ctx.path.startsWith('/@modules/')) {
    const moduleName = ctx.path.substr(10)
    const pkgPath = path.join(process.cwd(), 'node_modules', moduleName, 'package.json')
    const pkg = require(pkgPath)
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  await next()
})

// 1. 开启静态文件服务器
app.use(async (ctx, next) => {
  await send(ctx, ctx.path, {
    root: process.cwd(),
    index: 'index.html'
  })
  await next()
})

// 4. 处理单文件组件
app.use(async (ctx, next) => {
  if(ctx.path.endsWith('.vue')) {
    const contents = await streamToString(ctx.body)
    const { descriptor } = compilerSFC.parse(contents) // 返回一个对象,成员descriptor、errors
    let code
    if (!ctx.query.type) { // 第一次请求,把单文件组件编译成一个对象
      code = descriptor.script.content
      // console.log('code', code)
      code = code.replace(/export\s+default\s+/g, 'const __script = ')
      code += `
import { render as __render } from "${ctx.path}?type=template"
__script.render = __render
export default __script
      `
    } else if (ctx.query.type === 'template') {
      const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
      code = templateRender.code
    }
    ctx.type = 'application/javascript'
    ctx.body = stringToStream(code) // 转化成流
  }
  await next()
})

// 2. 修改第三方模块的路径
app.use(async (ctx, next) => {
  if (ctx.type === 'application/javascript') {
    const contents = await streamToString(ctx.body)
    // import vue from 'vue'
    // import App from './App.vue'
    ctx.body = contents
    .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/') // 分组匹配,第一个分组中,from原样匹配form,\s+匹配一至多个空格,['"]匹配单引号或双引号。第二个分组中,?!标识不匹配这个分组的结果,也就是排除点开头或者\开头的情况
    .replace(/process\.env\.NODE_ENV/g, '"development"')// 替换process对象
  }
})


app.listen(4000)
console.log('Server running @ http://localhost:4000')

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

使用时先将cli项目link到全局,npm link

然后在vue3项目中执行my-vite-cli运行项目。vue3中的图片和样式模块导入代码注释掉了。
在这里插入图片描述

文章来源: blog.csdn.net,作者:爱玲姐姐,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/jal517486222/article/details/108689712

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。