From cf5f67463aa3a5fc102ff50fca9405ecabd0e979 Mon Sep 17 00:00:00 2001 From: fxy060608 Date: Wed, 6 Dec 2023 17:17:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(uvue):=20=E6=94=AF=E6=8C=81=20v-once?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/vOnce.spec.ts.snap | 43 ++++++ .../android/transforms/vOnce.spec.ts | 142 ++++++++++++++++++ .../plugins/android/uvue/compiler/codegen.ts | 13 +- .../plugins/android/uvue/compiler/index.ts | 2 + .../android/uvue/compiler/transform.ts | 12 +- 5 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 packages/uni-app-uts/__tests__/android/transforms/__snapshots__/vOnce.spec.ts.snap create mode 100644 packages/uni-app-uts/__tests__/android/transforms/vOnce.spec.ts diff --git a/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/vOnce.spec.ts.snap new file mode 100644 index 0000000000..dcd6d2fb15 --- /dev/null +++ b/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/vOnce.spec.ts.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`compiler: v-once transform as root node 1`] = ` +"_cache[0] ?? run((): VNode | null => { + setBlockTracking(-1) + _cache[0] = createElementVNode("view", utsMapOf({ id: foo }), null, 8 /* PROPS */, ["id"]) + setBlockTracking(1) + return _cache[0] as VNode | null +})" +`; + +exports[`compiler: v-once transform on component 1`] = ` +"createElementVNode("view", null, [ + _cache[0] ?? run((): VNode | null => { + setBlockTracking(-1) + _cache[0] = createVNode(_component_Comp, utsMapOf({ id: foo }), null, 8 /* PROPS */, ["id"]) + setBlockTracking(1) + return _cache[0] as VNode | null + }) +])" +`; + +exports[`compiler: v-once transform on nested plain element 1`] = ` +"createElementVNode("view", null, [ + _cache[0] ?? run((): VNode | null => { + setBlockTracking(-1) + _cache[0] = createElementVNode("view", utsMapOf({ id: foo }), null, 8 /* PROPS */, ["id"]) + setBlockTracking(1) + return _cache[0] as VNode | null + }) +])" +`; + +exports[`compiler: v-once transform on slot outlet 1`] = ` +"createElementVNode("view", null, [ + _cache[0] ?? run((): VNode | null => { + setBlockTracking(-1) + _cache[0] = renderSlot($slots, "default") + setBlockTracking(1) + return _cache[0] as VNode | null + }) +])" +`; diff --git a/packages/uni-app-uts/__tests__/android/transforms/vOnce.spec.ts b/packages/uni-app-uts/__tests__/android/transforms/vOnce.spec.ts new file mode 100644 index 0000000000..4a9f72ef3b --- /dev/null +++ b/packages/uni-app-uts/__tests__/android/transforms/vOnce.spec.ts @@ -0,0 +1,142 @@ +import { NodeTypes, SET_BLOCK_TRACKING, baseParse } from '@vue/compiler-core' +import { getBaseTransformPreset } from '../../../src/plugins/android/uvue/compiler/index' +import { transform } from '../../../src/plugins/android/uvue/compiler/transform' +import { CompilerOptions } from '../../../src/plugins/android/uvue/compiler/options' +import { generate } from '../../../src/plugins/android/uvue/compiler/codegen' +import { RENDER_SLOT } from '../../../src/plugins/android/uvue/compiler/runtimeHelpers' + +function transformWithOnce(template: string, options: CompilerOptions = {}) { + const ast = baseParse(template) + const [nodeTransforms, directiveTransforms] = getBaseTransformPreset() + transform(ast, { + nodeTransforms, + directiveTransforms, + ...options, + }) + return ast +} + +describe('compiler: v-once transform', () => { + test('as root node', () => { + const root = transformWithOnce(``) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `"view"`, + }, + }) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on nested plain element', () => { + const root = transformWithOnce(``) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `"view"`, + }, + }) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on component', () => { + const root = transformWithOnce(``) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `_component_Comp`, + }, + }) + expect(generate(root).code).toMatchSnapshot() + }) + + test('on slot outlet', () => { + const root = transformWithOnce(``) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: RENDER_SLOT, + }, + }) + expect(generate(root).code).toMatchSnapshot() + }) + + // v-once inside v-once should not be cached + test('inside v-once', () => { + const root = transformWithOnce(``) + expect(root.cached).not.toBe(2) + expect(root.cached).toBe(1) + }) + + // cached nodes should be ignored by hoistStatic transform + // test('with hoistStatic: true', () => { + // const root = transformWithOnce(``, { + // hoistStatic: true, + // }) + // expect(root.cached).toBe(1) + // expect(root.helpers).toContain(SET_BLOCK_TRACKING) + // expect(root.hoists.length).toBe(0) + // expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + // type: NodeTypes.JS_CACHE_EXPRESSION, + // index: 0, + // value: { + // type: NodeTypes.VNODE_CALL, + // tag: `"view"`, + // }, + // }) + // expect(generate(root).code).toMatchSnapshot() + // }) + + test('with v-if/else', () => { + const root = transformWithOnce(`

`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.IF, + // should cache the entire v-if/else-if/else expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION, + consequent: { + type: NodeTypes.VNODE_CALL, + tag: `"view"`, + }, + alternate: { + type: NodeTypes.VNODE_CALL, + tag: `"p"`, + }, + }, + }, + }) + }) + + test('with v-for', () => { + const root = transformWithOnce(``) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.FOR, + // should cache the entire v-for expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION, + }, + }) + }) +}) diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts index 92db461679..08b58f7332 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts @@ -198,7 +198,7 @@ const UTS_COMPONENT_ELEMENT_IMPORTS = `/*UTS-COMPONENTS-IMPORTS*/` export function generate( ast: RootNode, - options: CodegenOptions + options: CodegenOptions = {} ): CodegenResult { const context = createCodegenContext(ast, options) const { mode, deindent, indent, push, newline } = context @@ -822,21 +822,20 @@ function genConditionalExpression( function genCacheExpression(node: CacheExpression, context: CodegenContext) { const { push, helper, indent, deindent, newline } = context - push(`_cache[${node.index}] || (`) + push(`_cache[${node.index}] ?? run((): VNode | null => {`) if (node.isVNode) { indent() - push(`${helper(SET_BLOCK_TRACKING)}(-1),`) + push(`${helper(SET_BLOCK_TRACKING)}(-1)`) newline() } push(`_cache[${node.index}] = `) genNode(node.value, context) if (node.isVNode) { - push(`,`) newline() - push(`${helper(SET_BLOCK_TRACKING)}(1),`) + push(`${helper(SET_BLOCK_TRACKING)}(1)`) newline() - push(`_cache[${node.index}]`) + push(`return _cache[${node.index}] as VNode | null`) deindent() } - push(`)`) + push(`})`) } diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts index facde71b44..ad44c4b60c 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts @@ -32,6 +32,7 @@ import { transformElements } from './transforms/transformElements' import { transformStyle } from './transforms/transformStyle' import { transformVHtml } from './transforms/vHtml' import { transformMemo } from './transforms/vMemo' +import { transformOnce } from './transforms/vOnce' export type TransformPreset = [ NodeTransform[], @@ -43,6 +44,7 @@ export function getBaseTransformPreset( ): TransformPreset { return [ [ + transformOnce, transformIf, transformMemo, transformFor, diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts index aee0f3dcf7..ea6ed54120 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts @@ -270,10 +270,18 @@ export function transform(root: RootNode, options: TransformOptions) { const context = createTransformContext(root, options) traverseNode(root, context) createRootCodegen(root, context) + + // finalize meta information + root.helpers = new Set([...context.helpers.keys()]) root.components = [...context.components] - // @ts-ignore - root.elements = Array.from(context.elements) + root.directives = [...context.directives] root.imports = context.imports + // root.hoists = context.hoists + root.temps = context.temps + root.cached = context.cached + + // @ts-expect-error + root.elements = Array.from(context.elements) } export function isSingleElementRoot( -- GitLab