Просмотр исходного кода

feat(compiler-sfc): introduce `defineRender`

三咲智子 Kevin Deng 2 лет назад
Родитель
Сommit
d046962ab4

+ 47 - 0
packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineRender.spec.ts.snap

@@ -0,0 +1,47 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`defineRender() > JSX Element 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+      
+return () => <div />
+}
+
+})"
+`;
+
+exports[`defineRender() > function 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+      
+return () => <div />
+}
+
+})"
+`;
+
+exports[`defineRender() > identifier 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { renderFn } from './ctx'
+      
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+      
+      
+return renderFn
+}
+
+})"
+`;

+ 46 - 0
packages/compiler-sfc/__tests__/compileScript/defineRender.spec.ts

@@ -0,0 +1,46 @@
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+describe('defineRender()', () => {
+  test('JSX Element', () => {
+    const { content } = compile(
+      `
+      <script setup lang="tsx">
+      defineRender(<div />)
+      </script>
+    `,
+      { defineRender: true }
+    )
+    assertCode(content)
+    expect(content).toMatch(`return () => <div />`)
+    expect(content).not.toMatch('defineRender')
+  })
+
+  test('function', () => {
+    const { content } = compile(
+      `
+      <script setup lang="tsx">
+      defineRender(() => <div />)
+      </script>
+    `,
+      { defineRender: true }
+    )
+    assertCode(content)
+    expect(content).toMatch(`return () => <div />`)
+    expect(content).not.toMatch('defineRender')
+  })
+
+  test('identifier', () => {
+    const { content } = compile(
+      `
+      <script setup lang="ts">
+      import { renderFn } from './ctx'
+      defineRender(renderFn)
+      </script>
+    `,
+      { defineRender: true }
+    )
+    assertCode(content)
+    expect(content).toMatch(`return renderFn`)
+    expect(content).not.toMatch('defineRender')
+  })
+})

+ 1 - 0
packages/compiler-sfc/__tests__/utils.ts

@@ -31,6 +31,7 @@ export function assertCode(code: string) {
       plugins: [
         'typescript',
         ['importAttributes', { deprecatedAssertSyntax: true }],
+        'jsx',
       ],
     })
   } catch (e: any) {

+ 23 - 4
packages/compiler-sfc/src/compileScript.ts

@@ -55,6 +55,7 @@ import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
 import { analyzeScriptBindings } from './script/analyzeScriptBindings'
 import { isImportUsed } from './script/importUsageCheck'
 import { processAwait } from './script/topLevelAwait'
+import { DEFINE_RENDER, processDefineRender } from './script/defineRender'
 
 export interface SFCScriptCompileOptions {
   /**
@@ -105,6 +106,11 @@ export interface SFCScriptCompileOptions {
    * @default true
    */
   hoistStatic?: boolean
+  /**
+   * (**Experimental**) Enable macro `defineRender`
+   * @default false
+   */
+  defineRender?: boolean
   /**
    * (**Experimental**) Enable reactive destructure for `defineProps`
    * @default false
@@ -491,7 +497,8 @@ export function compileScript(
         processDefineProps(ctx, expr) ||
         processDefineEmits(ctx, expr) ||
         processDefineOptions(ctx, expr) ||
-        processDefineSlots(ctx, expr)
+        processDefineSlots(ctx, expr) ||
+        processDefineRender(ctx, expr)
       ) {
         ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
       } else if (processDefineExpose(ctx, expr)) {
@@ -529,7 +536,8 @@ export function compileScript(
             !isDefineProps && processDefineEmits(ctx, init, decl.id)
           !isDefineEmits &&
             (processDefineSlots(ctx, init, decl.id) ||
-              processDefineModel(ctx, init, decl.id))
+              processDefineModel(ctx, init, decl.id) ||
+              processDefineRender(ctx, init))
 
           if (
             isDefineProps &&
@@ -797,8 +805,19 @@ export function compileScript(
   }
 
   // 9. generate return statement
-  let returned
-  if (
+  let returned = ''
+  if (ctx.renderFunction) {
+    if (sfc.template) {
+      warnOnce(`<template> is ignored when using ${DEFINE_RENDER}().`)
+    }
+    if (ctx.renderFunction.type === 'JSXElement') {
+      returned = '() => '
+    }
+    returned += scriptSetup.content.slice(
+      ctx.renderFunction.start!,
+      ctx.renderFunction.end!
+    )
+  } else if (
     !options.inlineTemplate ||
     (!sfc.template && ctx.hasDefaultExportRender)
   ) {

+ 4 - 0
packages/compiler-sfc/src/script/context.ts

@@ -36,6 +36,7 @@ export class ScriptCompileContext {
   hasDefaultExportRender = false
   hasDefineOptionsCall = false
   hasDefineSlotsCall = false
+  hasDefineRenderCall = false
   hasDefineModelCall = false
 
   // defineProps
@@ -59,6 +60,9 @@ export class ScriptCompileContext {
   // defineOptions
   optionsRuntimeDecl: Node | undefined
 
+  // defineRender
+  renderFunction?: Node
+
   // codegen
   bindingMetadata: BindingMetadata = {}
   helperImports: Set<string> = new Set()

+ 34 - 0
packages/compiler-sfc/src/script/defineRender.ts

@@ -0,0 +1,34 @@
+import { Node } from '@babel/types'
+import { isCallOf } from './utils'
+import { ScriptCompileContext } from './context'
+import { warnOnce } from '../warn'
+
+export const DEFINE_RENDER = 'defineRender'
+
+export function processDefineRender(
+  ctx: ScriptCompileContext,
+  node: Node
+): boolean {
+  if (!isCallOf(node, DEFINE_RENDER)) {
+    return false
+  }
+
+  if (!ctx.options.defineRender) {
+    warnOnce(
+      `${DEFINE_RENDER}() is an experimental feature and disabled by default.\n` +
+        `To enable it, follow the RFC at https://github.com/vuejs/rfcs/discussions/TODO.`
+    )
+    return false
+  }
+
+  if (ctx.hasDefineRenderCall) {
+    ctx.error(`duplicate ${DEFINE_RENDER}() call`, node)
+  }
+
+  ctx.hasDefineRenderCall = true
+  if (node.arguments.length > 0) {
+    ctx.renderFunction = node.arguments[0]
+  }
+
+  return true
+}