• vite尝鲜-最新代码原理分析


    使用webpack在开发中,只改动一句代码,也需要数秒的热更新,这是因为webpack需要将所有的模块打包成一个一个或者多个模块,然后启动开发服务器,请求服务器时直接给予打包结果。这个过程随着项目的扩大,速度会变慢。然后vite来了。

    描述:针对Vue单页面组件的无打包开发服务器,可以直接在浏览器运行请求的vue文件

    特点:

    • 冷服务启动-使用ES6 import预览的时候不打包
    • 开发中热更新
    • 按需进行编译,不会刷新全部DOM

    vite

    使用vite创建vue3项目

    1、Npm 创建 vite项目

    npm init vite-app projectName

    2、Yarn 创建vite项目

    yarn create vite-app projectName

    3、vite创建react项目

    • 新建文件夹。

    • 进入文件夹中命令npm init vite-app --template react

    • 安装依赖 yarn

    • 运行 yarn dev

    问题1:既然去掉了webpack打包步骤,那么vite是如何处理这些模块的呢?

    当声明一个 script 标签类型为 module 时,

    <script type="module" src="/src/main.js"></script>

    浏览器就会像服务器发起一个GET http://localhost:3000/src/main.js请求main.js文件,

    浏览器请求到了main.js文件,检测到内部含有import引入的包,又会对其内部的 import引用发起 HTTP 请求获取模块的内容文件!如: GET http://localhost:3000/@modules/vue.js 如: GET http://localhost:3000/src/App.vue 其Vite 的主要功能就是通过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器渲染页面,vite整个过程中没有对文件进行打包编译,所以其运行速度比原始的webpack开发编译速度快出许多!

    • 在浏览器中启动了一个socket服务,实时的接受一系列的指令,根据指令再处理相应的逻辑。
    // src/node/serve/index.ts
    export async function createServer(
      inlineConfig: InlineConfig = {}
    ): Promise<ViteDevServer> {
      // 创建serve服务
      const app = connect() as Connect.Server
      const ws = createWebSocketServer(httpServer, logger)
    
        const watchOptions = serverConfig.watch || {}
      const watcher = chokidar.watch(root, {
        ignored: [
          '**/node_modules/**',
          '**/.git/**',
          ...(watchOptions.ignored || [])
        ],
        ...
      }) as FSWatcher
    }

    createWebSocketServer处理

    // src/node/serve/ws.ts
    export function createWebSocketServer(
      server: Server,
      logger: Logger
    ): WebSocketServer {
      // 启动一个webSocket服务
      const wss = new WebSocket.Server({ noServer: true })
        server.on('upgrade', (req, socket, head) => {
        ...
        }
      })
      // 通知客户端链接成功,需要请求文件
      wss.on('connection', (socket) => {
        socket.send(JSON.stringify({ type: 'connected' }))
        ...
      })
    }
    
    • server端负责在执行的各个阶段向客户端发送指令
    // src/linet/client.ts
    async function handleMessage(payload: HMRPayload) {
      switch (payload.type) {
        case 'connected': // scoket链接成功
          console.log(`[vite] connected.`)
          setInterval(() => socket.send('ping'), __HMR_TIMEOUT__)
          break
        case 'update':// js文件更新
         ...
         ...
          break
        case 'custom'://自定义
         ...
          break
        case 'full-reload': //网页重刷新
          ...
          break
        case 'prune': //移除模块
          ...
          break
        case 'error':
         ...
          break
        default:
          const check: never = payload
          return check
      }
    }

    当vite文件监听系统监听到.vue组件发生变化之后,就会去解析编译.vue组件,并向client发送一条对应指令,并把编译后的代码也发送给client

    问题2:为什么给vue的模块加一个前缀@modules ?

    import { createApp } from 'vue'

    编译器能够自动从node_modules寻找vue这个模块,是因为npm install时,编译器存储了vue别名,因此可直接去node_modules中读取。

    但浏览器环境并没有执行这个过程,因此依然会从当前文件的同级路径下寻找vue这个文件,如果文件不存在,则报404错误,因此我们要把 node_modules 变成浏览器环境可识别的位置,即 /@modules/

    vue模块安装在node_modules中,浏览器ES Module是无法直接获取到项目下node_modules目录中的文件。所以viteimport都做了一层处理,重写了前缀使其带有@modules

    问题3:既然浏览器直接请求了.vue 文件,那么文件内容是如何做出解析的呢?

    唯一编译.vue文件,被解析成render函数返回给浏览器渲染页面。当Vite遇到一个.vue后缀的文件时。由于.vue模板文件的特殊性,它被分割成template,css,脚本模块三个模块进行分别处理。最后放入script,template,css发送多个请求获取。

    单页面文件的请求都是以*.vue作为请求路径结尾,当服务器接收到这种特点的http请求,主要处理

    • 根据ctx.path确定请求具体的vue文件
    • 使用parseSFC解析该文件,获得descriptor,一个descriptor包含了这个组件的基本信息,包括templatescriptstyles等属性

    然后根据descriptorctx.query.type选择对应类型的方法,处理后返回

    // plugin-vue/src/index.ts
    export default function vuePlugin(rawOptions: Options = {}): Plugin {
        ...
       transform(code, id) {
          const { filename, query } = parseVueRequest(id)
         ...
          if (!query.vue) {
           ...
          } else {
             // 使用parseSFC解析该文件
            const descriptor = getDescriptor(filename)!
             // 根据`descriptor`和`ctx.query.type`选择对应类型解析的方法
            if (query.type === 'template') {
              return transformTemplateAsModule(code, descriptor, options, this)
            } else if (query.type === 'style') {
              ...
            }
          }
        }
    }
    // plugin-vue/src/template.ts
    export function transformTemplateAsModule(
    ...
    ) {
     ...
      if (options.devServer && !options.isProduction) {
        returnCode += `
    import.meta.hot.accept(({ render }) => {
          __VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, render)
        })`
      }
      return {
        code: returnCode,
        map: result.map as any
      }
    }

    总结:

    1、默认采用ES 6原生模块

    2、默认会给vue的模块加一个前缀@modules import { createApp } from '/@modules/vue.js'

    3、解析.vue文件

    vite的优雅之处就在于需要某个模块时动态引入,而不是提前打包,自然而然提高了开发体验

  • 相关阅读:
    轻松学习Linux之AWK使用初步
    轻松学习Linux之理解Shell的硬链接与软连接
    轻松学习Linux之自动执行任务
    轻松学习Linux系统安装篇之fdisk命令行工具的使用
    Leetcode-1030 Next Greater Node In Linked List(链表中的下一个更大节点)
    Leetcode-1028 Convert to Base -2(负二进制转换)
    Leetcode-1029 Binary Prefix Divisible By 5(可被 5 整除的二进制前缀)
    ACM模板——2的次方表
    ACM模板——快速幂
    ACM模板——素数相关
  • 原文地址:https://www.cnblogs.com/jaelynl/p/14282454.html
Copyright © 2020-2023  润新知