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')
|