From 93c6aa4c9080136c8b2df09adcc64795664aedc1 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 9 Oct 2019 17:32:58 -0400 Subject: [PATCH] feat: v-once Note: only compiler transform is tested - integration with runtime still needs to be tested. --- .../__tests__/transforms/vOnce.spec.ts | 30 +++++++++++++++++++ packages/compiler-core/src/index.ts | 2 ++ .../compiler-core/src/transforms/vOnce.ts | 15 ++++++++++ packages/compiler-core/src/utils.ts | 2 +- .../__tests__/transforms/vCloak.spec.ts | 12 ++++---- packages/runtime-core/src/createRenderer.ts | 13 +++++--- packages/shared/src/index.ts | 2 +- 7 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 packages/compiler-core/__tests__/transforms/vOnce.spec.ts create mode 100644 packages/compiler-core/src/transforms/vOnce.ts diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts new file mode 100644 index 00000000..44b6e690 --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts @@ -0,0 +1,30 @@ +import { parse, transform, ElementNode, CallExpression } from '../../src' +import { transformOnce } from '../../src/transforms/vOnce' +import { transformElement } from '../../src/transforms/transformElement' +import { createObjectMatcher } from '../testUtils' + +function transformWithCloak(template: string) { + const ast = parse(template) + transform(ast, { + nodeTransforms: [transformElement], + directiveTransforms: { + once: transformOnce + } + }) + return ast.children[0] as ElementNode +} + +describe('compiler: v-once transform', () => { + test('should add no props to DOM', () => { + const node = transformWithCloak(`
`) + const codegenArgs = (node.codegenNode as CallExpression).arguments + + // As v-cloak adds no properties the codegen should be identical to + // rendering a div with no props or reactive data (so just the tag as the arg) + expect(codegenArgs[1]).toMatchObject( + createObjectMatcher({ + $once: `[true]` + }) + ) + }) +}) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index e1fdec2b..e1c967fe 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -13,6 +13,7 @@ import { transformBind } from './transforms/vBind' import { defaultOnError, createCompilerError, ErrorCodes } from './errors' import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot' import { optimizeText } from './transforms/optimizeText' +import { transformOnce } from './transforms/vOnce' export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions @@ -60,6 +61,7 @@ export function baseCompile( directiveTransforms: { on: transformOn, bind: transformBind, + once: transformOnce, ...(options.directiveTransforms || {}) // user transforms } }) diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts new file mode 100644 index 00000000..b83e0405 --- /dev/null +++ b/packages/compiler-core/src/transforms/vOnce.ts @@ -0,0 +1,15 @@ +import { + DirectiveTransform, + createObjectProperty, + createSimpleExpression +} from '@vue/compiler-core' + +export const transformOnce: DirectiveTransform = dir => { + return { + props: createObjectProperty( + createSimpleExpression(`$once`, true, dir.loc), + createSimpleExpression('true', false) + ), + needRuntime: false + } +} diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 6ffae2ca..83a71bb8 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -63,7 +63,7 @@ export const walkJS: typeof walk = (ast, walker) => { } export const isSimpleIdentifier = (name: string): boolean => - !/^\d|[^\w]/.test(name) + !/^\d|[^\$\w]/.test(name) export function getInnerRange( loc: SourceLocation, diff --git a/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts b/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts index a9c84a46..955dde73 100644 --- a/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts @@ -1,26 +1,24 @@ import { parse, transform, - CompilerOptions, - ElementNode + ElementNode, + CallExpression } from '@vue/compiler-core' import { transformCloak } from '../../src/transforms/vCloak' import { transformElement } from '../../../compiler-core/src/transforms/transformElement' -import { CallExpression } from '../../src' -function transformWithCloak(template: string, options: CompilerOptions = {}) { +function transformWithCloak(template: string) { const ast = parse(template) transform(ast, { nodeTransforms: [transformElement], directiveTransforms: { cloak: transformCloak - }, - ...options + } }) return ast.children[0] as ElementNode } -describe('compiler: `v-cloak` transform', () => { +describe('compiler: v-cloak transform', () => { test('should add no props to DOM', () => { const node = transformWithCloak(`
`) const codegenArgs = (node.codegenNode as CallExpression).arguments diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 8211a4bd..647a4ac9 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -177,10 +177,15 @@ export function createRenderer< optimized: boolean = false ) { // patching & not same type, unmount old tree - if (n1 != null && !isSameType(n1, n2)) { - anchor = getNextHostNode(n1) - unmount(n1, parentComponent, parentSuspense, true) - n1 = null + if (n1 != null) { + if (!isSameType(n1, n2)) { + anchor = getNextHostNode(n1) + unmount(n1, parentComponent, parentSuspense, true) + n1 = null + } else if (n1.props && n1.props.$once) { + console.log(111) + return + } } const { type, shapeFlag } = n2 diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 6e1b90c1..485be989 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -43,7 +43,7 @@ export const isPlainObject = (val: any): val is object => const vnodeHooksRE = /^vnode/ export const isReservedProp = (key: string): boolean => - key === 'key' || key === 'ref' || vnodeHooksRE.test(key) + key === 'key' || key === 'ref' || key === '$once' || vnodeHooksRE.test(key) const camelizeRE = /-(\w)/g export const camelize = (str: string): string => {