vue3 模板編譯 —— 30 行程式碼實現 .sync 修飾符

灬都是個謎發表於2023-04-20

? 前言

? v-bind 的 .sync 修飾符在 vue3 已移除

? 但為了更深入的瞭解 vue3 模板編譯 和 AST,所以我嘗試用 AST 實現 .sync 修飾符


? vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { transformSync } from './transformSync'

export default defineConfig({
  plugins: [
    vue({
      template: { compilerOptions: { nodeTransforms: [transformSync] } }
    })
  ]
})

? transformSync.ts

import { createSimpleExpression, DirectiveNode, SimpleExpressionNode, TemplateChildNode } from '@vue/compiler-core'
import { remove } from '@vue/shared'

const ELEMENT = 1
const DIRECTIVE = 7

// 建立事件表示式
// e.g. @arg="exp"
const createEventExpression = (arg?: SimpleExpressionNode, exp?: SimpleExpressionNode) => ({ type: DIRECTIVE, name: 'on', arg, exp, loc: undefined, modifiers: [] } as unknown as DirectiveNode)

/**
 * 將 `.sync` 修飾符轉換為 `@update:xxx`
 *
 * e.g.
 *
 * `<AAA :xxx.sync="value" />`
 *
 * ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
 *
 * `<AAA :xxx="value" @update:xxx="value = $event" />`
 */
export function transformSync(node: TemplateChildNode) {
  if (node.type === ELEMENT) {
    const { props } = node
    for (let i = 0; i < props.length; i++) {
      const dir = props[i]
      // 判斷屬性是否有 sync 修飾符
      if (dir.type == DIRECTIVE && dir.modifiers.includes('sync')) {
        remove(dir.modifiers, 'sync')
        const { arg, exp } = dir
        // @update:xxxx
        const name = createSimpleExpression('update:' + arg?.loc.source, true)
        // value = $event
        const val = createSimpleExpression(exp?.loc.source + ' = $event')
        // 為元素新增 @update:xxx="value = $event"
        props.push(createEventExpression(name, val))
      }
    }
  }
}

? 以上就是完整程式碼了

transformSync.ts 檔案去除註釋後大概是 30 行程式碼


? 讓我們來試試效果

<!-- MyButton.vue -->
<template>
  <button @click="$emit('update:count', count + 1)">count: {{ count }}</button>
</template>

<script setup lang="ts">
defineProps<{
  count: number
}>()
</script>
<!-- App.vue -->
<template>
  <my-button :count.sync="value" />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import MyButton from './MyButton.vue'

const value = ref(0)
</script>

? 執行成功 ?

? 線上 StackBlitz 編輯


? 點個贊吧 ✨ ?

相關文章