装包
npm i vue koa @vue/compiler-dom @vue/compiler-sfc @vue/composition-api
"@vue/compiler-dom": "^3.2.47",
"@vue/compiler-sfc": "^3.2.47",
"@vue/composition-api": "^1.7.1",
"koa": "^2.14.1",
"vue": "^3.2.47"
整体流程:
-
启动koa服务,vite就是一个http服务,解析各种资源文件
-
利用现代浏览器,支持script标签解析
tpe=module,核心就是利用现代浏览器能直接处理esm规范文件 -
全局注入
process.end.NODE_ENV变量,要啥补啥,在index.html中写入 -
项目源代码main.js文件代码内容,项目源代码App.vue文件内容
-
处理 /@module/vue 包引入
-
处理 .css 文件,就是加载css文件内容,用js创建style标签,设置innerHTML
-
处理 .vue 文件,SFC格式,要通过 @vue/compiler 编译,取出 template,script,style各个部分处理成js导出函数
-
继续处理 type=template ,使用@vue/compiler-dom包编译template,注意参数mode: module
-
处理 type=css,直接读取css内容,继续变为js创建style节点
启动koa服务,vite就是一个http服务,解析各种资源文件
const Koa = require('koa')
const app = new Koa()
app.use(context => {
context.type = 'application/javascript'
context.body = 'hello world'
})
app.listen(3000, () => {
console.log('koa start at 3000')
})
利用现代浏览器,支持script标签解析 tpe=module,核心就是利用现代浏览器能直接处理esm规范文件
// index.html文件中直接引入main.js
<script type="module" src="main.js"></script>
全局注入process.end.NODE_ENV变量,要啥补啥,在index.html中写入
<script>
window.process = {
env: {
NODE_ENV: 'development'
}
}
window.__VUE_OPTIONS_API__ = true
window.__VUE_PROD_DEVTOOLS__ = true
</script>
项目源代码main.js文件代码内容
import { createApp, h } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')
项目源代码App.vue文件内容
<template>
<div>
<h1>
{{ count }}
</h1>
<button @click="add">点击</button>
</div>
</template>
<script>
import { ref } from 'vue'
import { defineComponent } from '@vue/composition-api'
export default defineComponent({
setup() {
const count = ref(0)
function add() {
count.value++
}
return {
count,
add
}
}
})
</script>
<style>
h1 {
color: red;
}
</style>
首先让koa服务,能解析index.html文件,返回html内容
const Koa = require('koa')
const path = require('path')
const fs = require('fs')
const compilerSfc = require('@vue/compiler-sfc')
const compilerDom = require('@vue/compiler-dom')
const app = new Koa()
app.use(context => {
const { request: { url, query } } = context
const routePath = url.split('?')[0]
// 处理 index.html 内容返回
if (routePath == '/') {
const content = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
context.type = 'text/html'
context.body = content
}
})
app.listen(3000, () => {
console.log('koa start at 3000')
})
此时,可以看到index.html内容已经返回到页面上,但是报错 main.js 是 404
处理 main.js 这类js文件 404 的载入问题,后端处理js文件,找到路径并返回
添加 .js 文件处理
else if (routePath.endsWith('.js')) {
const content = fs.readFileSync(path.resolve(__dirname, routePath.slice(1)), 'utf-8')
context.type = 'application/javascript'
context.body = replaceImportModule(content)
}
此时看到,main.js内容返回了,但是页面报错
function replaceImportModule(code) {
return code.replace(/([import|export]) (.*) from ['|"]([^'"\.]+)['|"]/g, function (a, b, c, d) {
return `${b} ${c} from '/@module/${d}'`
})
}
原因是,main.js 中引入包 这句import { createApp, h } from 'vue',浏览器无法识别,这里我们要改为 import { createApp, h } from '/@module/vue' ,让浏览器再次请求 /@module/vue 这个文件,后端再次处理
所以添加函数 replaceImportModule 修改包引入这种代码的格式
处理 /@module/vue 包引入
在 /@module/vue 这个路径下,用 @module的表明是要在 node_modules 目录下找到vue包,然后根据 package.json文件的module字段,找到实际esm的文件
else if (routePath.startsWith('/@module')) {
const moduleName = routePath.replace('/@module/', '')
const modulePath = path.resolve(__dirname, 'node_modules', moduleName)
console.log(modulePath)
const pkg = require(path.join(modulePath, 'package.json'))
const moduleEsmPath = path.join(modulePath, pkg.module)
const content = fs.readFileSync(moduleEsmPath, 'utf-8')
context.type = 'application/javascript'
context.body = replaceImportModule(content)
}
可以看到,vue包和其他内部包也找到了
处理 .css 文件,就是加载css文件内容,用js创建style标签,设置innerHTML
else if (routePath.endsWith('.css')) {
const content = fs.readFileSync(path.resolve(__dirname, routePath.slice(1)), 'utf-8')
context.type = 'application/javascript'
context.body = `
const style = document.createElement('style')
style.setAttribute('type','text/css')
style.innerHTML = \`${content}\`
document.body.append(style)
`
}
可以看到在main.js中css文件引入也解决了
处理 .vue 文件,SFC格式,要通过 @vue/compiler 编译,取出 template,script,style各个部分处理成js导出函数
通过 @vue/compiler-sfc 的parse函数,编译后的 descriptor 对象,里面有script,template,styles对象,就表示各个部分,先只能导出 script部分,template和styles都还是使用import的方式,等待二次请求再处理
else if (routePath.indexOf('.vue') > -1) {
const sfcFileContent = fs.readFileSync(path.resolve(__dirname, routePath.slice(1)), 'utf-8')
const { descriptor } = compilerSfc.parse(sfcFileContent)
console.log(descriptor)
let content = descriptor.script.content.replace('export default defineComponent', `
import { render } from '${routePath}?type=template'\r\n
import '${routePath}?type=css'\r\n
const script = defineComponent`)
content += `
script.render = render\r\n
export default script\r\n`
context.type = 'application/javascript'
context.body = replaceImportModule(content)
}
相当于比如请求 App.vue 文件
经过上面之后返回的内容就是
import { render } from './App.vue?type=template'
import './App.vue?type=css'
const script = {
setup(){
...
}
}
script.render = render
export default script
继续处理 type=template ,使用@vue/compiler-dom包编译template,注意参数mode: module
else if (query.type == 'template') {
const sfcFileContent = fs.readFileSync(path.resolve(__dirname, routePath.slice(1)), 'utf-8')
const { descriptor } = compilerSfc.parse(sfcFileContent)
const template = compilerDom.compile(descriptor.template.content, { mode: "module" })
context.type = 'application/javascript'
context.body = replaceImportModule(template.code)
}
这个请求就是上面 import { render } from './App.vue?type=template' 触发的,也要注意是使用 replaceImportModule,因为内部也有包引入
处理 type=css,直接读取css内容,继续变为js创建style节点
else if (query.type == 'css') {
const sfcFileContent = fs.readFileSync(path.resolve(__dirname, routePath.slice(1)), 'utf-8')
const { descriptor } = compilerSfc.parse(sfcFileContent)
const content = descriptor.styles.reduce((code, it) => {
return code + it.content
}, '')
context.type = 'application/javascript'
context.body = `
const style = document.createElement('style')
style.innerHTML = \`${content}\`
document.body.append(style)
`
}
效果
剩余,处理 scss 其他文件格式,处理 jsx ,ts 等,相同编译,另外一个重点是热更新
在实际vite中,有几点不同:
-
不是if else 这么写是通过中间件处理不同格式
-
vite开发环境通过esbuild的能力处理jsx和ts、js 编译成 es6
-
vite生产环境是rollup打包