Browse Source

perf(compiler-vapor): inline static component literal props (#14838)

edison 4 weeks ago
parent
commit
260b2e9d27

+ 4 - 4
packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap

@@ -5,7 +5,7 @@ exports[`compiler: transition > basic 1`] = `
 const t0 = _template("<h1>foo")
 
 export function render(_ctx) {
-  const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+  const n1 = _createComponent(_VaporTransition, { persisted: "" }, {
     "default": () => {
       const n0 = t0()
       _applyVShow(n0, () => (_ctx.show))
@@ -21,7 +21,7 @@ exports[`compiler: transition > inject persisted when child has v-show 1`] = `
 const t0 = _template("<div>")
 
 export function render(_ctx) {
-  const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+  const n1 = _createComponent(_VaporTransition, { persisted: "" }, {
     "default": () => {
       const n0 = t0()
       _applyVShow(n0, () => (_ctx.ok))
@@ -72,8 +72,8 @@ const t0 = _template("<h1>foo")
 
 export function render(_ctx) {
   const n1 = _createComponent(_VaporTransition, {
-    appear: () => (""),
-    persisted: () => ("")
+    appear: "",
+    persisted: ""
   }, {
     "default": () => {
       const n0 = t0()

+ 33 - 8
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap

@@ -65,6 +65,21 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
 }"
 `;
 
+exports[`compiler: element transform > component > dynamic non-literal prop values stay as getter sources 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Foo = _resolveComponent("Foo")
+  const n0 = _createComponentWithFallback(_component_Foo, {
+    foo: () => (_ctx.bar),
+    obj: () => ({ a: 1 }),
+    fn: () => (() => _ctx.bar),
+    onClick: () => _ctx.foo
+  }, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: element transform > component > generate multi root component 1`] = `
 "import { createComponent as _createComponent, template as _template } from 'vue';
 const t0 = _template("123", false, true)
@@ -208,14 +223,24 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
 }"
 `;
 
+exports[`compiler: element transform > component > static literal bind props 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Foo = _resolveComponent("Foo")
+  const n0 = _createComponentWithFallback(_component_Foo, { literal: "bar" }, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: element transform > component > static props 1`] = `
 "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
 
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
   const n0 = _createComponentWithFallback(_component_Foo, {
-    id: () => ("foo"),
-    class: () => ("bar")
+    id: "foo",
+    class: "bar"
   }, null, true)
   return n0
 }"
@@ -239,7 +264,7 @@ exports[`compiler: element transform > component > v-bind="obj" after static pro
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
   const n0 = _createComponentWithFallback(_component_Foo, {
-    id: () => ("foo"),
+    id: "foo",
     $: [
       () => (_ctx.obj)
     ]
@@ -267,7 +292,7 @@ exports[`compiler: element transform > component > v-bind="obj" between static p
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
   const n0 = _createComponentWithFallback(_component_Foo, {
-    id: () => ("foo"),
+    id: "foo",
     $: [
       () => (_ctx.obj),
       { class: () => ("bar") }
@@ -449,8 +474,8 @@ exports[`compiler: element transform > dynamic component > component keeps both
 "import { createComponent as _createComponent } from 'vue';
 
 export function render(_ctx, $props, $emit, $attrs, $slots) {
-  const n0 = _createComponent(_ctx.Comp, { is: () => ("Parent") })
-  const n1 = _createComponent(_ctx.Comp, { is: () => ("Parent") })
+  const n0 = _createComponent(_ctx.Comp, { is: "Parent" })
+  const n1 = _createComponent(_ctx.Comp, { is: "Parent" })
   return [n0, n1]
 }"
 `;
@@ -478,7 +503,7 @@ exports[`compiler: element transform > dynamic component > normal component with
 
 export function render(_ctx) {
   const _component_custom_input = _resolveComponent("custom-input")
-  const n0 = _createComponentWithFallback(_component_custom_input, { is: () => ("foo") }, null, true)
+  const n0 = _createComponentWithFallback(_component_custom_input, { is: "foo" }, null, true)
   return n0
 }"
 `;
@@ -488,7 +513,7 @@ exports[`compiler: element transform > dynamic component > normal component with
 
 export function render(_ctx) {
   const _component_custom_input = _resolveComponent("custom-input")
-  const n0 = _createComponentWithFallback(_component_custom_input, { is: () => ("foo") }, null, true)
+  const n0 = _createComponentWithFallback(_component_custom_input, { is: "foo" }, null, true)
   return n0
 }"
 `;

+ 24 - 8
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts

@@ -196,8 +196,8 @@ describe('compiler: element transform', () => {
 
       expect(code).toMatchSnapshot()
       expect(code).contains(`{
-    id: () => ("foo"),
-    class: () => ("bar")
+    id: "foo",
+    class: "bar"
   }`)
 
       expect(ir.block.dynamic.children[0].operation).toMatchObject({
@@ -240,6 +240,23 @@ describe('compiler: element transform', () => {
       })
     })
 
+    test('static literal bind props', () => {
+      const { code } = compileWithElementTransform(`<Foo :literal="'bar'" />`)
+      expect(code).toMatchSnapshot()
+      expect(code).contains(`literal: "bar"`)
+    })
+
+    test('dynamic non-literal prop values stay as getter sources', () => {
+      const { code } = compileWithElementTransform(
+        `<Foo :foo="bar" :obj="{ a: 1 }" :fn="() => bar" @click="foo" />`,
+      )
+      expect(code).toMatchSnapshot()
+      expect(code).contains(`foo: () => (_ctx.bar)`)
+      expect(code).contains(`obj: () => ({ a: 1 })`)
+      expect(code).contains(`fn: () => (() => _ctx.bar)`)
+      expect(code).contains(`onClick: () => _ctx.foo`)
+    })
+
     test('v-bind="obj"', () => {
       const { code, ir } = compileWithElementTransform(`<Foo v-bind="obj" />`)
       expect(code).toMatchSnapshot()
@@ -264,7 +281,7 @@ describe('compiler: element transform', () => {
       )
       expect(code).toMatchSnapshot()
       expect(code).contains(`{
-    id: () => ("foo"),
+    id: "foo",
     $: [
       () => (_ctx.obj)
     ]
@@ -310,7 +327,7 @@ describe('compiler: element transform', () => {
       )
       expect(code).toMatchSnapshot()
       expect(code).contains(`{
-    id: () => ("foo"),
+    id: "foo",
     $: [
       () => (_ctx.obj),
       { class: () => ("bar") }
@@ -611,7 +628,7 @@ describe('compiler: element transform', () => {
       expect(helpers).toContain('resolveComponent')
       expect(helpers).not.toContain('resolveDynamicComponent')
       expect(code).toContain(
-        '_createComponentWithFallback(_component_custom_input, { is: () => ("foo") }, null, true)',
+        '_createComponentWithFallback(_component_custom_input, { is: "foo" }, null, true)',
       )
       expect(ir.block.dynamic.children[0].operation).toMatchObject({
         type: IRNodeTypes.CREATE_COMPONENT_NODE,
@@ -633,9 +650,8 @@ describe('compiler: element transform', () => {
       )
       expect(code).toMatchSnapshot()
       expect(
-        code.match(
-          /_createComponent\(_ctx\.Comp, \{ is: \(\) => \("Parent"\) \}\)/g,
-        )?.length,
+        code.match(/_createComponent\(_ctx\.Comp, \{ is: "Parent" \}\)/g)
+          ?.length,
       ).toBe(2)
     })
   })

+ 39 - 4
packages/compiler-vapor/src/generators/component.ts

@@ -69,7 +69,7 @@ export function genCreateComponent(
   const { root, props, slots, once } = operation
   const rawSlots = genRawSlots(slots, context)
   const [ids, handlers] = processInlineHandlers(props, context)
-  const rawProps = context.withId(() => genRawProps(props, context), ids)
+  const rawProps = context.withId(() => genRawProps(props, context, true), ids)
 
   const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
     (acc, { name, value }: InlineHandler) => {
@@ -175,6 +175,7 @@ function processInlineHandlers(
 export function genRawProps(
   props: IRProps[],
   context: CodegenContext,
+  directStaticLiteralProps = false,
 ): CodeFragment[] | undefined {
   const staticProps = props[0]
   if (isArray(staticProps)) {
@@ -185,10 +186,16 @@ export function genRawProps(
       staticProps,
       context,
       genDynamicProps(props.slice(1), context),
+      directStaticLiteralProps,
     )
   } else if (props.length) {
     // all dynamic
-    return genStaticProps([], context, genDynamicProps(props, context))
+    return genStaticProps(
+      [],
+      context,
+      genDynamicProps(props, context),
+      directStaticLiteralProps,
+    )
   }
 }
 
@@ -196,6 +203,7 @@ function genStaticProps(
   props: IRPropsStatic,
   context: CodegenContext,
   dynamicProps?: CodeFragment[],
+  directStaticLiteralProps = false,
 ): CodeFragment[] {
   const args: CodeFragment[][] = []
 
@@ -285,7 +293,15 @@ function genStaticProps(
     }
 
     // normal (non-handler) props
-    args.push(genProp(prop, context, true))
+    args.push(
+      genProp(
+        prop,
+        context,
+        true,
+        true,
+        directStaticLiteralProps && isDirectStaticLiteralProp(prop),
+      ),
+    )
 
     // v-model on component: synthesize onUpdate:* and modifiers props, and
     // dedupe/merge with user provided @update:* handlers.
@@ -410,6 +426,7 @@ function genProp(
   context: CodegenContext,
   isStatic?: boolean,
   wrapHandler = true,
+  directStaticLiteral = false,
 ) {
   const values = genPropValue(prop.values, context)
   return [
@@ -424,11 +441,29 @@ function genProp(
           wrapHandler /* wrapInGetter */,
         )
       : isStatic
-        ? ['() => (', ...values, ')']
+        ? directStaticLiteral
+          ? values
+          : ['() => (', ...values, ')']
         : values),
   ]
 }
 
+/**
+ * Top-level raw props can carry literal values directly for static primitives.
+ * The runtime accepts both literal values and getter functions, but literals
+ * avoid re-evaluation overhead. Keep handlers, v-model, merged values, and
+ * dynamic expressions as getter sources to maintain reactivity and merge semantics.
+ */
+function isDirectStaticLiteralProp(prop: IRProp): boolean {
+  return (
+    prop.key.isStatic &&
+    prop.values.length === 1 &&
+    prop.values[0].isStatic &&
+    !prop.handler &&
+    !prop.model
+  )
+}
+
 function genRawSlots(slots: IRSlots[], context: CodegenContext) {
   if (!slots.length) return
   const staticSlots = slots[0]