一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
第四章 - 编译单文件组件
书接上回 【来实现一个简单的 Vite 吧】!第三章 - 加载第三方模块,接下来我们来处理 编译单文件组件的问题。
浏览器无法处理我们在 main.js 中引用的单文件模块和样式模块,浏览器只能够处理js模块,所以单文件模块,需要在我们的服务器中转换成js文件
参考
接下来,我们打开浏览器参考 vite 中怎么处理单文件组件,其他的模块就先按下不表。
从这里我们可以看见,在我们请求单文件组件的时候,服务器会编译单文件组件,并且把编译好的文件返回给浏览器,然后我们会再次请求一次 图中标记的模块
这次的请求是为了,让服务器将单文件转换为render函数。然后再 App.vue中再使用这个函数
编译单文件组件
Vue3中给我们提供了一个编译的工具compiler-sfc
,尤大大真好!
1.插件安装一下
```js
npm i @vue/compiler-sfc
```
2. 创建中间件
```js
const compilerSFC = require('@vue/compiler-sfc')
...
// 4.处理单文件组件
app.use(async (ctx, next) => {
await next()
})
```
3. 判断是否是单文件组件
去判断是否是单文件组件,也就是请求的路劲是否是.vue
结尾
if(ctx.path.endsWith('.vue')) {}
小小的一行代码就能搞定,诶嘿
4. 获取文件内容
接下来我们需要获取 ctx.body 中的内容,然后调用compiler-sfc
的parse
方法
// 需要转换流
const contents = await stremToString(ctx.body)
const { descriptor } = compilerSFC.parse(contents)
这里的 parse 和 Vue2中的还有点不一样, Vue2中parse返回的是 AST 对象,这里也是一个对象,这个对象中有两个成员,分别是:单文件组件的描述对象和 编译过程中生成的错误对象
5. 们处理请求不包含 type的请求
单文件的处理有两次,这次我们处理请求不包含 type的请求的时候
let code
// 如果没有type请求,那就是第一次请求
if(!ctx.query.type) {
code = descriptor.script.content
console.log(code)
}
这里我们,打印一下 code 的代码来进行查看一下
然后,我们在开启vite中的代码,我们要把这个输出的内容改成和Vite一样的,说白了就是抄袭!
抄袭了上面的代码之后,就有了如下的输出
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
`
6. 文本转为流文件
const { Readable } = require('stream')
...
const stringToStream = text => {
const stream = new Readable()
stream.push(text)
stream.push(null) // 标记结束
return stream
}
...
// 4.处理单文件组件
app.use(async (ctx, next) => {
if(ctx.path.endsWith('.vue')) {
// 需要转换流
const contents = await stremToString(ctx.body)
const { descriptor } = compilerSFC.parse(contents)
let code
// 如果没有type请求,那就是第一次请求
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
`
}
ctx.type = 'application/javascript'
ctx.body = stringToStream(code)
}
await next()
})
7. 编译
if (ctx.query.type === 'template') {
const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
code = templateRender.code
}
然后修改 中间件2的 部分内容
// 2. 当把静态文件返回给浏览器之前判断是否是**js模块**,如果是的话就修改模块路径为`/@/modules/${模块名称}`
app.use(async (ctx, next) => {
if(ctx.type === 'application/javascript') {
...
ctx.body = contents
.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
.replace(/process\.env\.NODE_ENV/g, '"development"')
}
})
到这里,我们的代码就写完了
这里,我们还没有处理,样式和图片模块,之后的话,我们会进行一些处理的
目前完整代码: gitee.com/liuyinghao1…