一文带你彻底了解vite

前言

大家好,在上次我们已经把webpack从基础使用到原理分析讲过了,今天我们来聊一聊vite,vite为什么快。

在上篇关于webpack中已经提到了,webpack会把我们的项目构建好然后交给浏览器,那么这个过程就包含了js、css等等资源的构建,例如需要请求某一份资源,这份资源中又要请求另一份资源,构建完成才交给浏览器。

那么vite是怎么做的呢?

vite

vite读取项目代码后,他就直接把这份唯一的html文件输出给浏览器了,让浏览器来加载,那么在浏览器加载html时就会碰到一系列的引入,那么他就向vite发请求需要这一份js,js又向项目中取,拿到这份js后再交给浏览器,如此往复。这么看,vite就像是一个后端的服务器(所以你能明白我们配置vite.config.js用来处理跨域做代理了吗),这也是为什么我们vue3+vite构建一个新项目后,f12打开就能看见明明还没开始写代码,就有很多个资源的请求了。

这就是webpack和vite在构建理念上的不同之处了,在交给浏览器之前webpac会让浏览器等着,将资源全部加载完毕后才交付(从头到尾将代码读明白,甚至还会把浏览器识别不了的语法帮你降低版本)

构建vite

那么接下来我们来看看vite是如何打造的,原理是什么样的,我们来自己做一个vite

项目结构:

js 复制代码
npm i vue

index.html此处用module类型是因为如果不用这种方式引入,import语法是没办法识别的

js 复制代码
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="app">
    
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

main.js

js 复制代码
import {createApp} from "vue";
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

我们通过标签引入这份js代码,浏览器会当成一个资源请求,但我们并没有这个接口,所以会报一个404错误,但是我们用vite+vue也是一样的,那么vite在这个过程中起到了什么作用?事实上就是想办法让这个接口存在

那么我们在myVite根目录下创建一个simple-vite.js来模拟一下 simple-vite.js

js 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path')

const server = http.createServer((req, res) => {
    const {url,query} = req
    if (url === '/'){
        res.writeHead(200, {'Content-Type': 'text/html'})
        let html = fs.readFileSync('./index.html', 'utf-8')
        res.end(html)
    }else if (url.endsWith('.js')) { // /src/main.js
        const p = path.resolve(__dirname, url.slice(1))
        console.log(p)
        res.writeHead(200, {'Content-Type': 'application/javascript'})
        let content = fs.readFileSync(p, 'utf-8')
        res.end(content)
    }
})

server.listen(5173, () => {
    console.log('项目运行在5173')
})
js 复制代码
npx nodemon simple-vite.js

然后访问localhost:5173就会自动请求index.html进入到index.html后又会要main.js这个资源,此时就不会再报404的错误了,因为我们有这个接口,并且拿到了main.js这份资源。

但是,错误又出现了。

这种错误通常是没有正确设置响应头导致返回的文件类型不正确或者是网页中引用的模块脚本也就是我们的import要来的文件路径不正确或者是文件不存在

确实如此,我们import 一份vue的以后是from"vue"这里不是一个路径,我们必须告诉浏览器这份资源的正确路径才能够找到,因此我们需要一个方法来重写import,将其更改为一个路径。例如去node_modules/vue中寻找资源

js 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path')

function rewriteImport(content){
    return content.replace(/ from ['"](.*)['"]/g, function(s0,s1){
        if(s1[0] !== '.' && s1[0] !== '/'){
            return ` from '/@module/${s1}'`
        }else {
            return s0
        }
    })
}

const server = http.createServer((req, res) => {
    const {url,query} = req
    if (url === '/'){
        res.writeHead(200, {'Content-Type': 'text/html'})
        let html = fs.readFileSync('./index.html', 'utf-8')
        res.end(html)
    }else if (url.endsWith('.js')) { // /src/main.js
        const p = path.resolve(__dirname, url.slice(1))
        console.log(p)
        res.writeHead(200, {'Content-Type': 'application/javascript'})
        let content = fs.readFileSync(p, 'utf-8')
        res.end(rewriteImport(content))
    }
})

server.listen(5173, () => {
    console.log('项目运行在5173')
})

此时,不仅请求了main.js,当读到main.js后又会看见import xx from vue,此时会去请求vue资源。

那么接下来的工作就简单起来了,其实就是不断的写接口,如果你的路径是我们改写的@modules/xxx那就去node_modules下面找。

js 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path')

function rewriteImport(content){
    return content.replace(/ from ['"](.*)['"]/g, function(s0,s1){
        if(s1[0] !== '.' && s1[0] !== '/'){
            return ` from '/@module/${s1}'`
        }else {
            return s0
        }
    })
}

const server = http.createServer((req, res) => {
    const {url,query} = req
    if (url === '/'){
        res.writeHead(200, {'Content-Type': 'text/html'})
        let html = fs.readFileSync('./index.html', 'utf-8')
        res.end(html)
    }else if (url.endsWith('.js')) { // /src/main.js
        const p = path.resolve(__dirname, url.slice(1))
        console.log(p)
        res.writeHead(200, {'Content-Type': 'application/javascript'})
        let content = fs.readFileSync(p, 'utf-8')
        res.end(rewriteImport(content))
    }else if (url.startsWith('/@module/')){
        const prefix = path.resolve(__dirname, 'node_modules',url.replace('/@module/', ''))
        const module = require(prefix + '/package.json').module // vue源代码路径
        const p = path.resolve(prefix, module) // vue完整路径
        const content = fs.readFileSync(p, 'utf-8')
        res.writeHead(200,{
            'Content-Type': 'application/javascript'
        })
        res.end(rewriteImport(content))
    }else if ()
})

server.listen(5173, () => {
    console.log('项目运行在5173')
})

能够看见,main.js请求了,vue源代码也请求到了,剩下的就是css和vue的文件如何请求,接下来还是else if继续写接口对吧?但是写else if 拿vue后缀的请求,请求到了浏览器也没办法读懂,因此需要对vue代码进行编译,但是我们已经拿到vue的源码,vue源码中有编译器。

js 复制代码
const http = require('http')
const fs = require('fs')
const path = require('path')
const compilerSfc = require('@vue/compiler-sfc')
const compilerDom = require('@vue/compiler-dom')

function rewriteImport(content) { // ++++新增
    return content.replace(/ from ['|"]([^'"]+)['|"]/g, function(s0, s1) { // 找到  from 'vue' 中的  'vue'
        if (s1[0] !== '.' && s1[1] !== '/') {
            return ` from '/@modules/${s1}'`
        } else {
            return s0
        }
    })
}


const server = http.createServer((req, res) => {
    const { url } = req
    const query = new URL(req.url, `http://${req.headers.host}`).searchParams;

    if (url === '/') {
        res.writeHead(200, {
            'content-type': 'text/html'
        })
        let content = fs.readFileSync('./index.html', 'utf-8')
        res.end(content)

    } else if (url.endsWith('.js')) {  // '/src/main.js'
        const p = path.resolve(__dirname, url.slice(1))
        res.writeHead(200, {
            'content-type': 'application/javascript'
        })
        let content = fs.readFileSync(p, 'utf-8')
        res.end(rewriteImport(content))

    } else if (url.startsWith('/@modules/')) {  // '/@modules/vue'
        const prefix = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))
        const module = require(prefix + '/package.json').module
        const p = path.resolve(prefix, module)
        const content = fs.readFileSync(p, 'utf-8')
        res.writeHead(200, {
            'content-type': 'application/javascript'
        })
        res.end(rewriteImport(content))
    } else if (url.indexOf('.vue') !== -1) {
        const p = path.resolve(__dirname, url.split('?')[0].slice(1))
        const { descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf8'))
        // 问我要 App.vue 的 js 部分
        if (!query.get('type')) {
            res.writeHead(200, {'Content-Type': 'application/javascript'})
            const content = `
        ${rewriteImport(descriptor.script.content.replace('export default', 'const __script = '))} 
        import { render as __render } from "${url}?type=template" 
        __script.render = __render 
        export default __script
      `
            res.end(content)
        } else if (query.get('type') === 'template') {  // 问我要 App.vue 的 template 部分
            const template = descriptor.template
            const render = compilerDom.compile(template.content, {mode: 'module'}).code
            res.writeHead(200, {'Content-Type': 'application/javascript'})
            res.end(rewriteImport(render))
        }
    } else if (url.endsWith('.css')) {
        const p = path.resolve(__dirname, url.slice(1))
        const file = fs.readFileSync(p, 'utf8')
        const content = `
      const css = "${file.replace(/\n/g, '')}"
      let link = document.createElement('style')
      link.setAttribute('type', 'text/css')
      document.head.appendChild(link)
      link.innerHTML = css
      export default css
    `
        res.writeHead(200, {'Content-Type': 'application/javascript'})
        res.end(content)
    }
})




server.listen(5173, () => {
    console.log('项目运行在 5173');
})

小结

所以,vite为什么快?

Vite相比于Webpack之所以构建快是因为,Vite借助新版本浏览器可以读懂模块化语法的特点,将项目中的模块化引入统一以一个又一个http请求的方式响应给浏览器,这样做的好处就是省去了网络包构建过程中递归做依赖收集的耗时步骤,又因为Vite是开发环境的工具,绝大多数情况下我们不用不考虑兼容性,不会有人开发时还用老版本的浏览器吧!

当然vite快,这并不是绝对的,如果一个项目有几千几万个资源文件,那发这么多个http请求能快到哪里呢?

宏观结论:vite更快

相关推荐
会发光的猪。18 分钟前
【 ElementUI 组件Steps 步骤条使用新手详细教程】
前端·javascript·vue.js·elementui·前端框架
我家媳妇儿萌哒哒19 分钟前
el-table合并单元格之后,再进行隔行换色的且覆盖表格行鼠标移入的背景色的实现
前端·javascript·elementui
baiduguoyun34 分钟前
react的import 导入语句中的特殊符号
前端·react.js
前端青山35 分钟前
webpack指南
开发语言·前端·javascript·webpack·前端框架
NiNg_1_2341 小时前
ECharts实现数据可视化入门详解
前端·信息可视化·echarts
励志前端小黑哥2 小时前
有了Miniconda,再也不用担心nodejs、python、go的版本问题了
前端·python
喵叔哟2 小时前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特2 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解2 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~2 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf