Vite的介绍
Vite
Vite是一个面向现代浏览器的一个更轻、更快的web开发应用
基于ECMAScript标准原生模块系统(ESM)实现
Vite项目依赖项
Vite
@vue/compiler-sfc
Vite的特点
快速冷启动
模块热更新
按需编译
开箱即用
vite serve
vue-cli-service serve
vite只有当文件请求时才会去编译相应的模块
vite只有在请求时才会去编译文件,文件修改也只会编译当前文件
webpack HMR会以这个文件为入口重新build一次,所有涉及的依赖都会被重新加载一遍
vite build
Rollup 打包
动态导入
使用webpack的原因
浏览器环境不支持模块化
零散的模块文件会产生大量的http请求(http2可以解决通过复用链接)
开箱即用
Typescript内置支持
less/sass/stylus/postcss内置支持 需要单独安装依赖
jsx
Web Assembly
Vite的核心功能
vite在启动时,会把当前项目作为静态服务器的根目录,并拦截服务器的部分文件请求,当遇到浏览器不能识别的模块时会进行编译,通过websocked实现hmr
Vite的实现原理
静态web服务器
npm init -y 复制代码
在package.json文件中设置name为vite-cli即脚手架的启动命令为vite-cli
首先启动一个web服务器
#!/usr/bin/env node const Koa = require('koa') const send = require('koa-send') const app = new Koa() app.use(async (ctx,next) => { await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' }) await next() }) app.listen(3000, () => { console.log('serve listen 3000') }) 复制代码
使用npm link或yarn link 将这个脚手架工具安装到本地全局
npm link # 启动 vite-cli 复制代码
处理javascript请求
// stream转换成字符串 function streamToString(text) { return new Promise((resolve, reject) => { let chunks = [] text.on('data', chunk => chunks.push(chunk)) text.on('error', reject) text.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))) }) } app.use(async (ctx, next) => { if (ctx.type === 'application/javascript') { // ctx.body 是stream类型,需转换成字符串 ctx.body = await streamToString(ctx.body) } await next() }) 复制代码
加载第三方模块
首先需要将上面JavaScript代码中import的第三方包路径修改为'/@modules/',然后对第三方模块路径进行处理
// 处理第三方包路径 应该放在最前面 app.use(async (ctx, next) => { if (ctx.path.startsWith('/@modules/')) { let moduleName = ctx.path.substr(10) let modulePath = path.resolve(process.cwd(), 'node_modules', moduleName) let pkg = require(path.resolve(modulePath, 'package.json')) ctx.path = path.join('/node_modules', moduleName, pkg.module) } await next() }) ... app.use(async (ctx, next) => { if (ctx.type === 'application/javascript') { // ctx.body 是stream类型 let text = await streamToString(ctx.body) // 修改第三方模块包的路径 ctx.body = text.replace(/(from\s+['"])(?!.\/)/g, '$1/@modules/') } await next() }) 复制代码
编译单文件组件
首先需要修改vue文件格式为以下格式
import {render as __render} from "/aa.vue?type=template" __script.render = __render export default __script 复制代码
第二次请求vue文件时,输出编译后的代码
// 编译文件 const build = 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 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') { let template = compilerSfc.compileTemplate({ source: descriptor.template.content, filename: descriptor.filename, id: ctx.path }) code = template.code } ctx.body = stringToStream(code) ctx.type = 'application/javascript' } await next() } 复制代码
处理外部引入的css文件 首先需要将js代码中引入的css文件路径替换为xxx@import
app.use(async (ctx, next) => { if (ctx.type === 'application/javascript') { // ctx.body 是stream类型 let text = await streamToString(ctx.body) // 修改第三方模块包的路径 ctx.body = text.replace(/(from\s+['"])(?![.\/])/g, '$1/@modules/') .replace(/process\.env\.NODE_ENV/g, '"develop"') // css .replace(/(import\s+['"][\w\.\/]+\.css)(['"])/g,'$1?import$2') } await next() }) 复制代码
然后把css代码添加到html样式中
... if (ctx.path.endsWith('.css')) { const css = await streamToString(ctx.body) let code = ` import {updateStyle} from '/build/util/style.js' const css = ${JSON.stringify(css)} updateStyle(css) export default css ` ctx.body = stringToStream(code) ctx.type = 'application/javascript' } ... export function updateStyle(content,id){ const style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = content document.head.appendChild(style) } 复制代码
完整代码
入口文件
#!/usr/bin/env node const Koa = require('koa') const send = require('koa-send') const path = require('path') const { static, npmHandler, build } = require('./middlewares/index.js') const { streamToString } = require('./util') const app = new Koa() // 处理第三方依赖 app.use(npmHandler) // 处理静态资源 app.use(static) app.use(build) // 修改第三方模块依赖 app.use(async (ctx, next) => { if (ctx.type === 'application/javascript') { // ctx.body 是stream类型 let text = await streamToString(ctx.body) // 修改第三方模块包的路径 ctx.body = text.replace(/(from\s+['"])(?![.\/])/g, '$1/@modules/') .replace(/process\.env\.NODE_ENV/g, '"develop"') .replace(/(import\s+['"][\w\.\/]+\.css)(['"])/g,'$1?import$2') } await next() }) app.listen(3000) console.log('server listen http://localhost:3000') 复制代码
middlewares
const send = require('koa-send') const path = require('path') const compilerSfc = require('@vue/compiler-sfc') const { streamToString, stringToStream } = require('../util') // 静态资源 const static = async (ctx, next) => { await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' }) await next() } // 处理第三方依赖包 const npmHandler = async (ctx, next) => { if (ctx.path.startsWith('/@modules/')) { let moduleName = ctx.path.substr(10) let modulePath = path.resolve(process.cwd(), 'node_modules', moduleName) let pkg = require(path.resolve(modulePath, 'package.json')) ctx.path = path.join('/node_modules', moduleName, pkg.module) } await next() } // 编译文件 const build = 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 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') { let template = compilerSfc.compileTemplate({ source: descriptor.template.content, filename: descriptor.filename, id: ctx.path }) code = template.code console.log(descriptor.filename) } ctx.body = stringToStream(code) ctx.type = 'application/javascript' } else if (ctx.path.endsWith('.css')) { const css = await streamToString(ctx.body) let code = ` import {updateStyle} from '/build/util/style.js' const css = ${JSON.stringify(css)} updateStyle(css) export default css ` ctx.body = stringToStream(code) ctx.type = 'application/javascript' } await next() } module.exports = { static, npmHandler, build } 复制代码
utils
const { Readable } = require('stream') // stream 转换成 string function streamToString(text) { return new Promise((resolve, reject) => { let chunks = [] text.on('data', chunk => chunks.push(chunk)) text.on('error', reject) text.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))) }) } function stringToStream(text) { const stream = new Readable() stream.push(text) stream.push(null) return stream } module.exports = { streamToString, stringToStream } 复制代码
style
export function updateStyle(content,id){ const style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = content document.head.appendChild(style) }
作者:shibin
链接:https://juejin.cn/post/7021772569552355335