文章目录 vite简介:1、快速冷启动(对比vue-cli):2、模块热更新 HMR:3、按需编译 buildvite build:使用webpack打包原因(打包起因): 4、开箱即用:vite特性总结: vite原理1、静态服务器2、处理模块处理的路径,如 from ‘vue’ -> from ‘/@modules/vue.js’3、加载第三方模块,如 /@modules/vue.js4、处理单文件模块 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 nodeconst 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’)