Skip to content

理解模板编译器

Kawaiko mascot - question
什么是编译器?

“编译器”就是翻译器。它把便于人类编写的格式(模板)转换为便于机器处理的格式(h 函数)!

实际上,到目前为止我们已经拥有了运行所需的一切(?)

到目前为止,我们已经实现了响应式系统,虚拟 DOM 和组件。 虽然这些都非常小且不实用,但可以毫不夸张地说,我们已经理解了运行所需的整体配置元素。 虽然每个元素本身的功能都不足,但感觉我们已经表面上过了一遍。

从本章开始,我们将实现模板功能,使其更接近 Vue.js。但是,这些只是为了改善 DX,不会影响运行时。(严格来说,编译器优化可能会有影响,但由于这不是重点,我们假设它没有影响。)
更具体地说,我们将扩展开发者接口以改善 DX,并"最终将其转换为我们迄今为止制作的内部实现"。

这次我们想要实现的开发者接口

目前,开发者接口看起来像这样。

ts
const MyComponent: Component = {
  props: { someMessage: { type: String } },

  setup(props: any, { emit }: any) {
    return () =>
      h('div', {}, [
        h('p', {}, [`someMessage: ${props.someMessage}`]),
        h('button', { onClick: () => emit('click:change-message') }, [
          'change message',
        ]),
      ])
  },
}

const app = createApp({
  setup() {
    const state = reactive({ message: 'hello' })
    const changeMessage = () => {
      state.message += '!'
    }

    return () =>
      h('div', { id: 'my-app' }, [
        h(
          MyComponent,
          {
            'some-message': state.message,
            'onClick:change-message': changeMessage,
          },
          [],
        ),
      ])
  },
})

目前,View 部分是使用 h 函数构建的。我们希望能够在 template 选项中编写模板,使其更接近原始 HTML。
但是,一次实现各种东西是困难的,所以让我们从有限的功能集开始。
现在,让我们将其分为以下任务:

  1. 能够渲染简单的标签,消息和静态属性。
ts
const app = createApp({ template: `<p class="hello">Hello World</p>` })
  1. 能够渲染更复杂的 HTML。
ts
const app = createApp({
  template: `
    <div>
      <p>hello</p>
      <button> click me! </button>
    </div>
  `,
})
  1. 能够使用在 setup 函数中定义的内容。
ts
const app = createApp({
  setup() {
    const count = ref(0)
    const increment = () => {
      count.value++
    }

    return { count, increment }
  },

  template: `
    <div>
      <p>count: {{ count }}</p>
      <button v-on:click="increment"> click me! </button>
    </div>
  `,
})

我们将进一步将每个任务分为更小的部分,但让我们大致分为这三个步骤。 让我们从步骤 1 开始。

编译器的作用

现在,我们的目标开发者接口看起来像这样。

ts
const app = createApp({ template: `<p class="hello">Hello World</p>` })

首先,让我们谈谈什么是编译器。 在编写软件时,您很快就会听到"编译器"这个词。 "编译"意味着翻译,在软件领域,它通常用于表示从高级描述翻译到低级描述。
您还记得本书开头的这个词吗?

为了方便起见,我们将更接近原始 JS 的称为"低级开发者接口"。 而且,重要的是要注意"开始实现时,从低级部分开始"。 这样做的原因是,在许多情况下,高级描述被转换为低级描述并运行。 换句话说,1 和 2 最终在内部转换为 3 的形式。 这种转换的实现称为"编译器"。

那么,为什么我们需要这个叫做编译器的东西呢?主要目的之一是"改善开发体验"。 至少,如果提供了一个有效的低级接口,就可以仅使用这些函数进行开发。 但是,考虑与功能无关的各种部分可能会很麻烦和困难,描述可能难以理解。因此,我们将仅重新开发接口部分,考虑用户的感受。

在这方面,Vue.js 的目标是"像原始 HTML 一样编写,并使用 Vue 提供的功能(指令等)方便地编写视图"。 而且,最终目标是 SFC。
最近,随着 jsx/tsx 的流行,Vue 也提供这些作为开发者接口的选项。但是,这次,让我们尝试实现 Vue 的原始模板。

我已经用长篇文章解释了它,但最终,我这次想要做的是实现将这样的代码翻译(编译)的能力:

ts
const app = createApp({ template: `<p class="hello">Hello World</p>` })

转换为这样:

ts
const app = createApp({
  render() {
    return h('p', { class: 'hello' }, ['Hello World'])
  },
})

为了进一步缩小范围,就是这部分:

ts
;`<p class="hello">Hello World</p>`
// ↓
h('p', { class: 'hello' }, ['Hello World'])
Kawaiko mascot - funny
模板的本质

模板最终会转换为 h 函数调用。也就是说,我们之前实现的 h 函数也会在这里派上用场!

让我们分几个阶段逐步实现它。

基于 MIT 许可证发布。