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 000000000..44b6e690f --- /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 e1fdec2b4..e1c967fec 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 000000000..b83e04057 --- /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 6ffae2caa..83a71bb8f 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 a9c84a465..955dde731 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 8211a4bdc..647a4ac90 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 6e1b90c17..485be989c 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 => {