文章目錄
-
- 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')