【来实现一个简单的 Vite 吧! 】第四章 - 编译单文件组件

1,099 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

第四章 - 编译单文件组件

书接上回 【来实现一个简单的 Vite 吧】!第三章 - 加载第三方模块,接下来我们来处理 编译单文件组件的问题。

浏览器无法处理我们在 main.js 中引用的单文件模块和样式模块,浏览器只能够处理js模块,所以单文件模块,需要在我们的服务器中转换成js文件

参考

接下来,我们打开浏览器参考 vite 中怎么处理单文件组件,其他的模块就先按下不表。

image.png

image.png

从这里我们可以看见,在我们请求单文件组件的时候,服务器会编译单文件组件,并且把编译好的文件返回给浏览器,然后我们会再次请求一次 图中标记的模块

image.png 这次的请求是为了,让服务器将单文件转换为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-sfcparse方法

 // 需要转换流
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 的代码来进行查看一下

image.png 然后,我们在开启vite中的代码,我们要把这个输出的内容改成和Vite一样的,说白了就是抄袭!

image.png

image.png 抄袭了上面的代码之后,就有了如下的输出

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"')
  }
})

到这里,我们的代码就写完了

image.png 这里,我们还没有处理,样式和图片模块,之后的话,我们会进行一些处理的 目前完整代码: gitee.com/liuyinghao1…