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;
    }
  }
相关推荐
Momo__3 小时前
Vue 3.6 Vapor Mode:跳过虚拟 DOM,性能极致优化
前端·vue.js
walking9574 小时前
重新学习前端之JavaScript
前端·vue.js·面试
walking9574 小时前
重新学习前端之HTML
前端·vue.js·面试
walking9574 小时前
重新学习前端之Vue
前端·vue.js·面试
泉城老铁4 小时前
springboot实现word转换pdf
vue.js·后端
walking9574 小时前
重新学习前端之Linux
前端·vue.js·面试
walking9574 小时前
重新学习前端之CSS
前端·vue.js·面试
walking9574 小时前
重新学习前端之Git
前端·vue.js·面试
Hello--_--World5 小时前
Vue指令:v-if vs v-show、v-if 与 v-for 的优先级冲突、自定义指令
前端·javascript·vue.js
Hello--_--World8 小时前
Vue:虚拟Dom
前端·javascript·vue.js