vue3复习-源码-迷你版vite

vite的优势

  • 由于webpack每次打包都是根据整个依赖图谱,重新打包成对应的bunlde文件,当项目大时,打包效率比较低。虽然也有更新但效果不明显。
  • vite使用的是es6的import方式,运行时按需引入对应的代码。所以只需要加载一部分文件的数据,效率高。

只要设置type="module"声明了script的js,就可以使用import的方式导入其他js

下面main.js 就可以正常的使用import

js 复制代码
<script type="module" src="/src/main.js"></script>

main.js

js 复制代码
import { createApp } from "vue"; //这里可以用import 写法
import App from './App.vue'//这里可以用import 写法

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

迷你vite实现原理

下面是main.js的代码

js 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
const app = createApp(App)
app.mount('#app')

App.vue

js 复制代码
<template>
 --{{val}}-- 
</template>

<script>
 const val = Vue.ref("oooo") 
</script>
 

我们看到主要要解决的是

  • import的路径问题:识别from 'vue' 里面的vue 具体是从哪里来的
  • vue文件识别问题:App.vue 文件的解析,js不能识别和加载
  • css文件识别问题:解析index.css ,js不能直接识别和加载

import的路径问题

启动一个koa服务,用于转化当前请求的文件地址

js 复制代码
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const app = new Koa()
app.use(async ctx=>{
  const {request:{url,query} } = ctx
if(url=='/'){
    ctx.type="text/html"
    let content = fs.readFileSync('./index.html','utf-8')
    
    ctx.body = content
  }
})
app.listen(10086, ()=>{
  console.log('服务启动')
})

我们一般启动一个vue服务后,index.html返回的内容是

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>xxxx</title>
</head>
<body>
  <h1>xxxxx</h1>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

修改koa代码 ,让他能正常返回html的内容

js 复制代码
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const app = new Koa()

app.use(async ctx=>{
  const {request:{url,query} } = ctx
  if(url=='/'){ // 返回首页html内容
      ctx.type="text/html"
      let content = fs.readFileSync('./index.html','utf-8')
      
      ctx.body = content
  }else if(url.endsWith('.js')){ //返回js路径 既访问的文件是: <script type="module" src="/src/main.js"></script>的 main.js
    // js文件
    const p = path.resolve(__dirname,url.slice(1))
    ctx.type = 'application/javascript'
    const content = fs.readFileSync(p,'utf-8')
    ctx.body = rewriteImport(content) // 替换路径
  }else if(url.startsWith('/@modules/')){ // 处理node_module 
    const prefix = path.resolve(__dirname,'node_modules',url.replace('/@modules/',''))
    const module = require(prefix+'/package.json').module
    const p = path.resolve(prefix,module)
    const ret = fs.readFileSync(p,'utf-8')
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(ret) // 递归调用
}
})


function rewriteImport(content){ //此方法用于 替换请求文件的路径 改成/@modules下面
  return content.replace(/ from ['|"]([^'"]+)['|"]/g, function(s0,s1){
    // . ../ /开头的,都是相对路径
    if(s1[0]!=='.'&& s1[1]!=='/'){
      return ` from '/@modules/${s1}'`
    }else{
      return s0
    }
  })
}



app.listen(10086, ()=>{
  console.log('服务启动')
})

请求的index.html

请求的main.js

请求的vue

vue3又会根据自己的import继续引入其他vue3运行所需要的框架

  • runtime-dom
  • runtime-core
  • shared
  • reactivity

vue文件识别

.vue中的三种类型

  • <template>
  • <scirpt>
  • <style>

原理就是把请求vue文件的时候,变成3个不同的import请求,通过url?type=xxx 区分

  • 如:url?type=template,就做template解析处理
  • 如:url?type=style,就做css解析处理
  • 如:url?type=js,就做js解析处理

我们使用@vue/compiler-sfc 来处理vue文件。

compiler-dom 处理<template>的转化

js 复制代码
const compilerSfc = require('@vue/compiler-sfc') // .vue
const compilerDom = require('@vue/compiler-dom') // 模板<template>处理

...
else if(url.indexOf('.vue')>-1){ //发现是.vue文件,把他转化为url?type=template 的二次请求
    
      // vue单文件组件
      const p = path.resolve(__dirname, url.split('?')[0].slice(1))
       // 使用vue自导的compile框架 解析单文件组件,等价于vue-loader
      const {descriptor} = compilerSfc.parse(fs.readFileSync(p,'utf-8')) 
      if(!query.type){
        ctx.type = 'application/javascript' 
        //descriptor.script.content 输出的是template内容
        console.log("vue come in",descriptor)
        ctx.body = `
    ${rewriteImport(descriptor.script.content.replace('export default ','const __script = '))}
    import { render as __render } from "${url}?type=template"
    __script.render = __render
    export default __script
        `
        console.log("ctx.body",ctx.body)
      }else if(query.type==='template'){ // 这里处理 url?type=template的逻辑
        // 模板内容
        const template = descriptor.template
        // 要在server端吧compiler做了
        const render = compilerDom.compile(template.content, {mode:"module"}).code
        ctx.type = 'application/javascript'
        ctx.body = rewriteImport(render) // 继续递归处理import内容
      }
    }
    

使用sfc转化后的App.vue,里面多了一个template的转化后的/App.vue?type=template请求

css文件识别

识别.css后缀,然后通过创建一个<style>标签插入css到html里面

js 复制代码
else if(url.endsWith('.css')){
        // console.log("css come in")
        const p = path.resolve(__dirname,url.slice(1))
        const file = fs.readFileSync(p,'utf-8')
        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
        `
        ctx.type = 'application/javascript'
        ctx.body = content
      }

扩展-热更新

通过WebSocket 实时通知客户端更新页面数据

js 复制代码
export function watch() {
    const watcher = chokidar.watch(appRoot, {
      ignored: ['**/node_modules/**', '**/.git/**'],
      ignoreInitial: true,
      ignorePermissionErrors: true,
      disableGlobbing: true,
    });
    watcher;
    return watcher;
  }
  export function handleHMRUpdate(opts: { file: string; ws: any }) {
    const { file, ws } = opts;
    const shortFile = getShortName(file, appRoot);
    const timestamp = Date.now();
    console.log(`[file change] ${chalk.dim(shortFile)}`);
    let updates;
    if (shortFile.endsWith('.css')) {
      updates = [
        {
          type: 'js-update',
          timestamp,
          path: `/${shortFile}`,
          acceptedPath: `/${shortFile}`,
        },
      ];
    }
    ws.send({
      type: 'update',
      updates,
    });
  }

实时处理更新的数据

js 复制代码
  async function handleMessage(payload: any) {
    switch (payload.type) {
      case 'connected':
        console.log(`[vite] connected.`);
        setInterval(() => socket.send('ping'), 30000);
        break;
      case 'update':
        payload.updates.forEach((update: Update) => {
          if (update.type === 'js-update') {
            fetchUpdate(update);
          } 
        });
        break;
    }
  }
相关推荐
会发光的猪。2 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客2 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
周全全2 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
ZwaterZ3 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
码农六六3 小时前
vue3封装Element Plus table表格组件
javascript·vue.js·elementui
徐同保3 小时前
el-table 多选改成单选
javascript·vue.js·elementui
快乐小土豆~~3 小时前
el-input绑定点击回车事件意外触发页面刷新
javascript·vue.js·elementui
周三有雨3 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
大霞上仙5 小时前
element ui table 每行不同状态
vue.js·ui·elementui
lv程序媛5 小时前
el-table表头前几列固定,后面几列根据接口返回的值不同展示不同
javascript·vue.js·elementui