天天看點

vite簡介、原理及簡單實作

文章目錄

    • vite簡介:
      • 1、快速冷啟動(對比vue-cli):
        • 2、子產品熱更新 HMR:
        • 3、按需編譯 build
          • vite build:
          • 使用webpack打包原因(打包起因):
        • 4、開箱即用:
          • vite特性總結:
    • vite原理
      • 1、靜态伺服器
      • 2、處理子產品處理的路徑,如 from 'vue' -> from '/@modules/vue.js'
      • 3、加載第三方子產品,如 /@modules/vue.js
      • 4、處理單檔案子產品
    • vite初步實作:

vite簡介:

vite基于es modules 實作,面向現代浏覽器的更輕、快的Web工具

解決webpack 開發階段 devServer 冷啟動時間過長,HMR 熱更新反應速度慢的問題;

1、快速冷啟動(對比vue-cli):

1、使用vite建立的項目就是一個普通的v3應用,相比于vue-cli建立的項目也少了很多配置檔案和依賴;

vite項目依賴:Vite、@vue/compiler-sfc

vite serve; vite build

2、vite是直接啟動伺服器,請求之後将檔案開始編譯傳遞給伺服器(即時編譯,按需速度會更快,開發階段無需存儲);

3、vue-cli是先打包好bundle存儲,請求之後發送bundle給伺服器解析,是以打包bundle的過程受項目大小和依賴的影響;

2、子產品熱更新 HMR:

vite HMR:立即編譯目前所修改的檔案

webpack HMR:會自動以這個檔案為入口重寫build一次,所有涉及到的依賴都會被加載一遍

3、按需編譯 build

vite build:

生産環境下使用rollup打包,還是會提前打包編譯到一起;

動态導入Dynamic import,隻支援現代浏覽器,可用Polyfill輔助;

使用webpack打包原因(打包起因):

浏覽器環境不支援子產品化; — 現代浏覽器對es module的支援

零散的子產品檔案産生大量的http請求; — http2 多路複用

4、開箱即用:

ts(内置),less/sass/stylus/postcss - 内置需單獨安裝,JSX,Web Assembly

vite特性總結:

快速冷啟動、子產品熱更新、按需編譯、開箱即用(避免loader、plugin配置)

vite原理

核心功能分析:靜态Web伺服器、編譯單檔案元件(攔截浏覽器不識别的子產品并處理)、HMR

1、靜态伺服器

基于 koa、koa-send

2、處理子產品處理的路徑,如 from ‘vue’ -> from ‘/@modules/vue.js’

ctx.type === ‘application/javascript’

contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
<!-- /?!/ 表示不比對的 $1 分組比對到的内容 -->
           

3、加載第三方子產品,如 /@modules/vue.js

校驗路由,調整ctx.path為實際位址:

const pkg = require(pkgPath)
ctx.path = path.join('/node_modules', moduleName, pkg.module)
// pkg.module 即pkg的package.json的入口檔案的值
           

4、處理單檔案子產品

單檔案元件編譯成一個選項對象,傳回給浏覽器;

把模闆編譯成render函數(ctx.requery.type===‘template’)

compilerSFC = require('@vue/compiler-sfc')
const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
code = templateRender.code
           

浏覽器下找不到process - /shared;開發環境下process.env.xx -> 對應值;如NODE_ENV -> development

vite初步實作:

#!/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) // null 辨別流寫完了
  return stream
}

// 3. 加載第三方子產品
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)
    // pkg.module 即pkg的package.json的入口檔案的值,也是一個路徑;如 vue對應 "module": "dist/vue.runtime.esm-bundler.js"
  }
  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)
    let code
    if (!ctx.query.type) {
      code = descriptor.script.content
      // console.log(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 // code即render函數
    }
    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/')
      .replace(/process\.env\.NODE_ENV/g, '"development"')
  }
})

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

繼續閱讀