![](https://img.haomeiwen.com/i10131721/57fdece47bf2b91d.png)
大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。。
写一个mini-vite
上节看了vite
是怎么加载文件的,本节使用koa
做服务器实现一个mini-vite
。
实现思路:首先这边所有文件都应该是服务器返回的,我们要针对不同的请求类型进行区别处理,例如:如果请求的是/
则返回index.html
项目的入口文件,且入口文件的js
文件引入需要设置成type="module"
以此来让浏览器支持es module语法
;
如果请求的是js
文件,解析js
文件,如果import
的模块是相对路径,则说明是项目路径下的文件,则请求该文件,如果不是说明是引用的node_modules
下的模块,然后需要读取对应的node_modules
下的package.json
文件,然后获取到入口文件,然后在进行加载(在此过程中如果继续需要了node_modules
中的模块,依旧照此进行处理);
如果请求的是vue
文件,我们需要在服务端使用vue
的模块编译将代码编译成ast
,其中ast
中可以获取到template
、script
、style
三部分的模块解析并且需要重新发送一个请求以此来处理模块的渲染。针对script
模块自定义一个变量并将其导出,针对template
模块,直接对代码进行拼接返回,针对style
模块,也是理应对应的loader
或者模块分析器进行处理,我的例子中只写了对template
和script
的处理。
实现服务器搭建
新建一个项目mini-vite
,打开目录,对项目进行初始化
npm init
一路回车,然后配置scripts
"scripts": {
"serve": "node ./vite/index.cjs"
},
在平级目录下新建vite/index.cjs
表明是服务端代码,搭建服务
npm i koa
const Koa = require("koa")
//创建server
function createServer() {
const app = new Koa()
app.use((ctx)=>{
ctx.body = 'Hello World';
})
app.listen(3000)
console.log("listen:", `http://127.0.0.1:3000`)
}
//创建服务
createServer()
![](https://img.haomeiwen.com/i10131721/a2ada0a8b39caa4c.png)
说明服务已经启动成功了。
实现文件的访问
直接在服务器处理里面加判断呗
app.use(async (ctx) => {
const url = ctx.url
if (url === "/") {
//解析html文件获取依赖
const html = fs.readFileSync(resolvePath(root, "./index.html"))
ctx.type = "html"
ctx.body = html
}
})
因为html
文件里面引入了main.js
但是我们没有对js
文件进行处理
![](https://img.haomeiwen.com/i10131721/14cfbd0d86077524.png)
处理js
文件
//判断是否是js
app.use(async (ctx) => {
const url = ctx.url
if (url.endsWith(".js")) {
//处理js请求
const file = resolveFileName(ctx.url)
let text = ""
if (url.indexOf("node_modules")) {
text = fs.readFileSync(resolvePath(root, `./${url}`, "utf-8"))
} else {
text = fs.readFileSync(resolvePath(root, `./${file}`, "utf-8"))
}
//读取到vue文件时,没有时去node_modules中查找
ctx.type = "application/javascript"
ctx.body = text.toString()
}
})
![](https://img.haomeiwen.com/i10131721/a4b332d93da934f1.png)
因为我们main.js
中引入了vue
,而浏览器只能根据相对路径去读取文件,加载vue
文件需要到node_modules
中去查找
main.js
文件
import { createApp, h } from "vue"
createApp({
render() {
return h("div", "hello vite")
},
}).mount("#app")
console.log("hello world")
处理node_modules
模块引入问题rewriteImport
方法,将匹配到的import xxx from "xxx"
替换为import xxx from "/@modules/xxx"
function rewriteImport(content) {
//匹配所有 import xxx from "xxx"
return content.replace(/ from ['"](.*)['"]/g, function (s1, s2) {
if (s2.startsWith("./") || s2.startsWith("/") || s2.startsWith("../")) {
//相对路径
return s1
} else {
return ` from '/@modules/${s2}'`
}
})
}
//处理js的地方,返回内容的时候需要重写一下import 语句
//判断是否是js
app.use(async (ctx) => {
const url = ctx.url
if (url.endsWith(".js")) {
//处理js请求
const file = resolveFileName(ctx.url)
let text = ""
if (url.indexOf("node_modules")) {
text = fs.readFileSync(resolvePath(root, `./${url}`, "utf-8"))
} else {
text = fs.readFileSync(resolvePath(root, `./${file}`, "utf-8"))
}
//读取到vue文件时,没有时去node_modules中查找
ctx.type = "application/javascript"
ctx.body = rewriteImport(text.toString())
}
})
将node_modules
中模块替换后,又提示找不到@modules
中的资源
![](https://img.haomeiwen.com/i10131721/60c468a8ee584a34.png)
处理@modules
资源
app.use(async (ctx) => {
const url = ctx.url
if (url.startsWith("/@modules/")) {
const moduleName = url.replace("/@modules/", "")
const prefix = path.join(__dirname, "../node_modules", moduleName)
// package.json中获取moduels字段
const module = require(prefix + "/package.json").module
const filePath = path.join(prefix, module)
const ret = fs.readFileSync(filePath, "utf-8")
ctx.type = "application/javascript"
ctx.body = rewriteImport(ret)
}
})
然后就能看到效果了。
![](https://img.haomeiwen.com/i10131721/5b883aa39209ca57.png)
看下如果main.js
中引入.vue
文件呢,会发现加载不到App.vue
,因为没有处理.vue
文件
![](https://img.haomeiwen.com/i10131721/02013ad729682a86.png)
处理.vue
文件
app.use(async (ctx) => {
const url = ctx.url
(url.endsWith(".vue")) {
//需要将vue文件中template转换成ast
const filePath = resolvePath(root, `./${url}`)
const content = fs.readFileSync(filePath, "utf-8")
ctx.type = "text/javascript"
ctx.body = content
}
})
确实是返回App.vue
文件了,但是浏览器不识别
![](https://img.haomeiwen.com/i10131721/4e830aeeaabcb2cc.png)
![](https://img.haomeiwen.com/i10131721/c27977bd97f4d08e.png)
所以需要利用vue
的编译模块对读取到的文件内容进行编译,生成一个ast
const {
parse,
compileTemplate,
} = require("../node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs")
app.use(async (ctx) => {
const url = ctx.url
if (url.endsWith(".vue")) {
//需要将vue文件中template转换成ast
const filePath = resolvePath(root, `./${url}`)
const content = fs.readFileSync(filePath, "utf-8")
const contentAst = parse(content)
console.log(contentAst)
const scriptContent = contentAst.descriptor.script
? contentAst.descriptor.script.content
: null
// const script = scriptContent.replace("export default","const _script = ")
//转换script\template\style
//script部分放在app.vue中,template部分放在aap.vye?type=template中
let code = ``
if (contentAst.descriptor.script.content) {
// code += contentAst.descriptor.script.content
code += contentAst.descriptor.script.content.replace(
/((?:^|\n|;)\s*)export default/,
"$1const __script="
)
}
//将content转成render函数
if (contentAst.descriptor.template.content) {
// code += `import './App.vue?type=template'`
const requestPath = ctx.url + `?type=template`
code += `\nimport {render as __render} from '${requestPath}'`
code += `\n__script.render=__render`
}
code += `\nexport default __script`
ctx.type = "text/javascript"
ctx.body = content
}
})
使用parse
生成ast
后的内容
![](https://img.haomeiwen.com/i10131721/f9085d4401fd8135.png)
上面只是生成了ast
,我们还需要将模板编译render
函数,因此需要在上面单独对type=template
类型的文件进行处理,这里type=template
是我们自己标记的,用来标识是模板文件
处理type=template
文件,compileTemplate
用来将模板编译成render
函数
app.use(async (ctx) => {
const url = ctx.url
if (ctx.request.query.type === "template") {
let queryUrl = ctx.url.indexOf("?")
let fileUrl = ctx.url.slice(0, queryUrl)
console.log("vue template request")
const filePath = resolvePath(root, `./${fileUrl}`)
let content = fs.readFileSync(filePath, "utf-8")
const contentAst = parse(content)
let { code } = compileTemplate({
source: contentAst.descriptor.template.content,
id: "app",
})
ctx.type = "text/javascript"
ctx.body = rewriteImport(`${code}`)
}
})
type=template
返回内容
![](https://img.haomeiwen.com/i10131721/6731b0fd0a00fc83.png)
至此处理.vue
文件终于成功了
![](https://img.haomeiwen.com/i10131721/45a2840891b7ca2e.png)
网友评论