diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 37d9d3147..f24f4c875 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -35,6 +35,21 @@ export function render(_ctx) { }" `; +exports[`compiler: v-once > on component 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent, insert as _insert, template as _template } from 'vue/vapor'; +const t0 = _template("
") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n1 = t0() + const n0 = _createComponent(_component_Comp, [ + { id: () => (_ctx.foo) } + ], null, null, null, true) + _insert(n0, n1) + return n1 +}" +`; + exports[`compiler: v-once > on nested plain element 1`] = ` "import { setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor'; const t0 = _template("
") @@ -47,6 +62,19 @@ export function render(_ctx) { }" `; +exports[`compiler: v-once > with v-for 1`] = ` +"import { createFor as _createFor, template as _template } from 'vue/vapor'; +const t0 = _template("
") + +export function render(_ctx) { + const n0 = _createFor(() => (_ctx.list), (_block) => { + const n2 = t0() + return [n2, () => {}] + }, null, null, null, true) + return n0 +}" +`; + exports[`compiler: v-once > with v-if 1`] = ` "import { createIf as _createIf, template as _template } from 'vue/vapor'; const t0 = _template("
") diff --git a/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts b/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts index 34c58358c..19c306aaa 100644 --- a/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts @@ -131,7 +131,25 @@ describe('compiler: v-once', () => { ]) }) - test.todo('on component') + test('on component', () => { + const { ir, code } = compileWithOnce(`
`) + expect(code).toMatchSnapshot() + expect(ir.block.effect).lengthOf(0) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + id: 0, + tag: 'Comp', + once: true, + }, + { + type: IRNodeTypes.INSERT_NODE, + elements: [0], + parent: 1, + }, + ]) + }) + test.todo('on slot outlet') test('inside v-once', () => { @@ -205,5 +223,16 @@ describe('compiler: v-once', () => { ]) }) - test.todo('with v-for') + test('with v-for', () => { + const { ir, code } = compileWithOnce(`
`) + expect(code).toMatchSnapshot() + expect(ir.block.effect).lengthOf(0) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.FOR, + id: 0, + once: true, + }, + ]) + }) }) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index ea626505c..74602d70e 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -34,7 +34,7 @@ export function genCreateComponent( const { vaporHelper } = context const tag = genTag() - const { root, slots, dynamicSlots } = oper + const { root, slots, dynamicSlots, once } = oper const rawProps = genRawProps(oper.props, context) return [ @@ -46,7 +46,8 @@ export function genCreateComponent( rawProps, slots && genSlots(slots, context), dynamicSlots && genDynamicSlots(dynamicSlots, context), - root && 'true', + root ? 'true' : false, + once && 'true', ), ...genDirectivesForElement(oper.id, context), ] diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index 96eef4067..738ef12e6 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -19,7 +19,7 @@ export function genFor( context: CodegenContext, ): CodeFragment[] { const { vaporHelper, genEffects } = context - const { source, value, key, index, render, keyProp } = oper + const { source, value, key, index, render, keyProp, once } = oper const rawValue = value && value.content const rawKey = key && key.content @@ -67,11 +67,18 @@ export function genFor( } genEffects.pop() - return [ NEWLINE, `const n${oper.id} = `, - ...genCall(vaporHelper('createFor'), sourceExpr, blockFn, getKeyFn), + ...genCall( + vaporHelper('createFor'), + sourceExpr, + blockFn, + getKeyFn, + false, // todo: getMemo + false, // todo: hydrationNode + once && 'true', + ), ] function genEffectInFor(effects: IREffect[]): CodeFragment[] { diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 27420d7f6..c86e7c2ea 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -82,6 +82,7 @@ export interface ForIRNode extends BaseIRNode { index?: SimpleExpressionNode keyProp?: SimpleExpressionNode render: BlockIRNode + once: boolean } export interface IRProp extends Omit { @@ -224,6 +225,7 @@ export interface CreateComponentIRNode extends BaseIRNode { asset: boolean root: boolean + once: boolean } export interface DeclareOldRefIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index f79102405..173cdb58f 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -106,6 +106,7 @@ function transformComponentElement( root, slots: context.slots, dynamicSlots: context.dynamicSlots, + once: context.inVOnce, }) context.slots = undefined context.dynamicSlots = undefined diff --git a/packages/compiler-vapor/src/transforms/vFor.ts b/packages/compiler-vapor/src/transforms/vFor.ts index 2abcfea36..0b896ba0b 100644 --- a/packages/compiler-vapor/src/transforms/vFor.ts +++ b/packages/compiler-vapor/src/transforms/vFor.ts @@ -63,6 +63,7 @@ export function processFor( index: index as SimpleExpressionNode | undefined, keyProp: keyProperty, render, + once: context.inVOnce, }) } } diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts index cf282706b..64180729f 100644 --- a/packages/runtime-vapor/src/apiCreateComponent.ts +++ b/packages/runtime-vapor/src/apiCreateComponent.ts @@ -14,6 +14,7 @@ export function createComponent( slots: Slots | null = null, dynamicSlots: DynamicSlots | null = null, singleRoot: boolean = false, + once: boolean = false, ) { const current = currentInstance! const instance = createComponentInstance( @@ -21,6 +22,7 @@ export function createComponent( singleRoot ? withAttrs(rawProps) : rawProps, slots, dynamicSlots, + once, ) setupComponent(instance, singleRoot) diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 362ac762c..e487e14ac 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -22,6 +22,7 @@ export const createFor = ( getKey?: (item: any, key: any, index?: number) => any, getMemo?: (item: any, key: any, index?: number) => any[], hydrationNode?: Node, + once?: boolean, ): Fragment => { let isMounted = false let oldBlocks: ForBlock[] = [] @@ -32,9 +33,12 @@ export const createFor = ( nodes: oldBlocks, [fragmentKey]: true, } - const update = getMemo ? updateWithMemo : updateWithoutMemo - renderEffect(() => { + once ? renderList() : renderEffect(renderList) + + return ref + + function renderList() { const source = src() const newLength = getLength(source) const oldLength = oldBlocks.length @@ -213,9 +217,7 @@ export const createFor = ( } ref.nodes = [(oldBlocks = newBlocks), parentAnchor] - }) - - return ref + } function mount( source: any, diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts index 7a5392424..f317002b0 100644 --- a/packages/runtime-vapor/src/apiCreateVaporApp.ts +++ b/packages/runtime-vapor/src/apiCreateVaporApp.ts @@ -47,6 +47,7 @@ export function createVaporApp( rootProps, null, null, + false, context, ) setupComponent(instance) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index d435fc561..bb98adc85 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -261,6 +261,7 @@ export function createComponentInstance( rawProps: RawProps | null, slots: Slots | null, dynamicSlots: DynamicSlots | null, + once: boolean = false, // application root node only appContext?: AppContext, ): ComponentInternalInstance { @@ -354,7 +355,7 @@ export function createComponentInstance( */ // [VaporLifecycleHooks.SERVER_PREFETCH]: null, } - initProps(instance, rawProps, !isFunction(component)) + initProps(instance, rawProps, !isFunction(component), once) initSlots(instance, slots, dynamicSlots) instance.emit = emit.bind(null, instance) diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 363f66726..e8224ab6a 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -81,6 +81,7 @@ export function initProps( instance: ComponentInternalInstance, rawProps: RawProps, isStateful: boolean, + once: boolean, ) { if (!rawProps) rawProps = [] else if (!isArray(rawProps)) rawProps = [rawProps] @@ -97,16 +98,16 @@ export function initProps( for (const key in options) { const getter = () => getDynamicPropValue(rawProps as NormalizedRawProps, key) - registerProp(instance, props, key, getter, true) + registerProp(instance, once, props, key, getter, true) } } else { const staticProps = rawProps[0] as StaticProps for (const key in options) { const rawKey = staticProps && getRawKey(staticProps, key) if (rawKey) { - registerProp(instance, props, key, staticProps[rawKey]) + registerProp(instance, once, props, key, staticProps[rawKey]) } else { - registerProp(instance, props, key, undefined, false, true) + registerProp(instance, once, props, key, undefined, false, true) } } } @@ -133,6 +134,7 @@ export function initProps( function registerProp( instance: ComponentInternalInstance, + once: boolean, props: Data, rawKey: string, getter?: (() => unknown) | (() => DynamicPropResult), @@ -158,10 +160,9 @@ function registerProp( ? () => withCast(getter!()) : getter! - Object.defineProperty(props, key, { - get, - enumerable: true, - }) + const descriptor: PropertyDescriptor = once ? { value: get() } : { get } + descriptor.enumerable = true + Object.defineProperty(props, key, descriptor) } }