feat(compiler-core): re-implement v-once to use cache mechanism
This commit is contained in:
parent
9291011456
commit
af5a8e1154
@ -21,6 +21,20 @@ export default function render() {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
|
||||
"
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
const _cache = _ctx.$cache
|
||||
return _cache[1] || (
|
||||
setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: codegen ConditionalExpression 1`] = `
|
||||
"
|
||||
return function render() {
|
||||
|
@ -380,4 +380,33 @@ describe('compiler: codegen', () => {
|
||||
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('CacheExpression w/ isVNode: true', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
cached: 1,
|
||||
codegenNode: createCacheExpression(
|
||||
1,
|
||||
createSimpleExpression(`foo`, false),
|
||||
true
|
||||
)
|
||||
}),
|
||||
{
|
||||
mode: 'module',
|
||||
prefixIdentifiers: true
|
||||
}
|
||||
)
|
||||
expect(code).toMatch(`const _cache = _ctx.$cache`)
|
||||
expect(code).toMatch(
|
||||
`
|
||||
_cache[1] || (
|
||||
setBlockTracking(-1),
|
||||
_cache[1] = foo,
|
||||
setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
`.trim()
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
@ -0,0 +1,101 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler: v-once transform as root node 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode } = _Vue
|
||||
const _cache = $cache
|
||||
|
||||
return _cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-once transform on component 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
const _cache = $cache
|
||||
|
||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-once transform on nested plain element 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
const _cache = $cache
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-once transform on slot outlet 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
const _cache = $cache
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = _renderSlot($slots, \\"default\\"),
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-once transform with hoistStatic: true 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
const _cache = $cache
|
||||
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_cache[1] || (
|
||||
_setBlockTracking(-1),
|
||||
_cache[1] = _createVNode(\\"div\\"),
|
||||
_setBlockTracking(1),
|
||||
_cache[1]
|
||||
)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
@ -387,7 +387,8 @@ describe('compiler: transform v-model', () => {
|
||||
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const args = (root.children[0] as ComponentNode).codegenNode!.arguments
|
||||
const args = ((root.children[0] as ComponentNode)
|
||||
.codegenNode as CallExpression).arguments
|
||||
// props
|
||||
expect(args[1]).toMatchObject({
|
||||
properties: [
|
||||
|
@ -1,28 +1,109 @@
|
||||
import { parse, transform, ElementNode, CallExpression } from '../../src'
|
||||
import {
|
||||
parse,
|
||||
transform,
|
||||
NodeTypes,
|
||||
generate,
|
||||
CompilerOptions
|
||||
} from '../../src'
|
||||
import { transformOnce } from '../../src/transforms/vOnce'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import {
|
||||
CREATE_VNODE,
|
||||
RENDER_SLOT,
|
||||
SET_BLOCK_TRACKING
|
||||
} from '../../src/runtimeHelpers'
|
||||
import { transformBind } from '../../src/transforms/vBind'
|
||||
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
|
||||
|
||||
function transformWithOnce(template: string) {
|
||||
function transformWithOnce(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
nodeTransforms: [transformOnce, transformElement, transformSlotOutlet],
|
||||
directiveTransforms: {
|
||||
once: transformOnce
|
||||
}
|
||||
bind: transformBind
|
||||
},
|
||||
...options
|
||||
})
|
||||
return ast.children[0] as ElementNode
|
||||
return ast
|
||||
}
|
||||
|
||||
describe('compiler: v-once transform', () => {
|
||||
test('should add no props to DOM', () => {
|
||||
const node = transformWithOnce(`<div v-once />`)
|
||||
const codegenArgs = (node.codegenNode as CallExpression).arguments
|
||||
test('as root node', () => {
|
||||
const root = transformWithOnce(`<div :id="foo" v-once />`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE
|
||||
}
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
expect(codegenArgs[1]).toMatchObject(
|
||||
createObjectMatcher({
|
||||
$once: `[true]`
|
||||
})
|
||||
)
|
||||
test('on nested plain element', () => {
|
||||
const root = transformWithOnce(`<div><div :id="foo" v-once /></div>`)
|
||||
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: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE
|
||||
}
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('on component', () => {
|
||||
const root = transformWithOnce(`<div><Comp :id="foo" v-once /></div>`)
|
||||
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: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE
|
||||
}
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('on slot outlet', () => {
|
||||
const root = transformWithOnce(`<div><slot v-once /></div>`)
|
||||
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: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_SLOT
|
||||
}
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
// cached nodes should be ignored by hoistStatic transform
|
||||
test('with hoistStatic: true', () => {
|
||||
const root = transformWithOnce(`<div><div v-once /></div>`, {
|
||||
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: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_VNODE
|
||||
}
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
@ -365,7 +365,7 @@ describe('compiler: transform component slots', () => {
|
||||
} else {
|
||||
const innerComp = (root.children[0] as ComponentNode)
|
||||
.children[0] as ComponentNode
|
||||
flag = innerComp.codegenNode!.arguments[3]
|
||||
flag = (innerComp.codegenNode as CallExpression).arguments[3]
|
||||
}
|
||||
if (shouldForce) {
|
||||
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
|
||||
|
@ -116,40 +116,45 @@ export interface BaseElementNode extends Node {
|
||||
isSelfClosing: boolean
|
||||
props: Array<AttributeNode | DirectiveNode>
|
||||
children: TemplateChildNode[]
|
||||
codegenNode: CallExpression | SimpleExpressionNode | undefined
|
||||
codegenNode:
|
||||
| CallExpression
|
||||
| SimpleExpressionNode
|
||||
| CacheExpression
|
||||
| undefined
|
||||
}
|
||||
|
||||
export interface PlainElementNode extends BaseElementNode {
|
||||
tagType: ElementTypes.ELEMENT
|
||||
codegenNode: ElementCodegenNode | undefined | SimpleExpressionNode // only when hoisted
|
||||
codegenNode:
|
||||
| ElementCodegenNode
|
||||
| undefined
|
||||
| SimpleExpressionNode // when hoisted
|
||||
| CacheExpression // when cached by v-once
|
||||
}
|
||||
|
||||
export interface ComponentNode extends BaseElementNode {
|
||||
tagType: ElementTypes.COMPONENT
|
||||
codegenNode: ComponentCodegenNode | undefined
|
||||
codegenNode: ComponentCodegenNode | undefined | CacheExpression // when cached by v-once
|
||||
}
|
||||
|
||||
export interface SlotOutletNode extends BaseElementNode {
|
||||
tagType: ElementTypes.SLOT
|
||||
codegenNode: SlotOutletCodegenNode | undefined
|
||||
codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once
|
||||
}
|
||||
|
||||
export interface TemplateNode extends BaseElementNode {
|
||||
tagType: ElementTypes.TEMPLATE
|
||||
codegenNode:
|
||||
| ElementCodegenNode
|
||||
| CodegenNodeWithDirective<ElementCodegenNode>
|
||||
| undefined
|
||||
codegenNode: ElementCodegenNode | undefined | CacheExpression
|
||||
}
|
||||
|
||||
export interface PortalNode extends BaseElementNode {
|
||||
tagType: ElementTypes.PORTAL
|
||||
codegenNode: ElementCodegenNode | undefined
|
||||
codegenNode: ElementCodegenNode | undefined | CacheExpression
|
||||
}
|
||||
|
||||
export interface SuspenseNode extends BaseElementNode {
|
||||
tagType: ElementTypes.SUSPENSE
|
||||
codegenNode: ElementCodegenNode | undefined
|
||||
codegenNode: ElementCodegenNode | undefined | CacheExpression
|
||||
}
|
||||
|
||||
export interface TextNode extends Node {
|
||||
@ -298,6 +303,7 @@ export interface CacheExpression extends Node {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION
|
||||
index: number
|
||||
value: JSChildNode
|
||||
isVNode: boolean
|
||||
}
|
||||
|
||||
// Codegen Node Types ----------------------------------------------------------
|
||||
@ -625,12 +631,14 @@ export function createConditionalExpression(
|
||||
|
||||
export function createCacheExpression(
|
||||
index: number,
|
||||
value: JSChildNode
|
||||
value: JSChildNode,
|
||||
isVNode: boolean = false
|
||||
): CacheExpression {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index,
|
||||
value,
|
||||
isVNode,
|
||||
loc: locStub
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ import {
|
||||
COMMENT,
|
||||
helperNameMap,
|
||||
RESOLVE_COMPONENT,
|
||||
RESOLVE_DIRECTIVE
|
||||
RESOLVE_DIRECTIVE,
|
||||
SET_BLOCK_TRACKING
|
||||
} from './runtimeHelpers'
|
||||
|
||||
type CodegenNode = TemplateChildNode | JSChildNode
|
||||
@ -247,6 +248,10 @@ export function generate(
|
||||
.join(', ')} } = _Vue`
|
||||
)
|
||||
newline()
|
||||
if (ast.cached > 0) {
|
||||
push(`const _cache = $cache`)
|
||||
newline()
|
||||
}
|
||||
newline()
|
||||
}
|
||||
} else {
|
||||
@ -625,7 +630,22 @@ function genSequenceExpression(
|
||||
}
|
||||
|
||||
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||
context.push(`_cache[${node.index}] || (_cache[${node.index}] = `)
|
||||
const { push, helper, indent, deindent, newline } = context
|
||||
push(`_cache[${node.index}] || (`)
|
||||
if (node.isVNode) {
|
||||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
newline()
|
||||
}
|
||||
push(`_cache[${node.index}] = `)
|
||||
genNode(node.value, context)
|
||||
context.push(`)`)
|
||||
if (node.isVNode) {
|
||||
push(`,`)
|
||||
newline()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
|
||||
newline()
|
||||
push(`_cache[${node.index}]`)
|
||||
deindent()
|
||||
}
|
||||
push(`)`)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ export function baseCompile(
|
||||
...options,
|
||||
prefixIdentifiers,
|
||||
nodeTransforms: [
|
||||
transformOnce,
|
||||
transformIf,
|
||||
transformFor,
|
||||
...(prefixIdentifiers
|
||||
@ -62,7 +63,6 @@ export function baseCompile(
|
||||
directiveTransforms: {
|
||||
on: transformOn,
|
||||
bind: transformBind,
|
||||
once: transformOnce,
|
||||
model: transformModel,
|
||||
...(options.directiveTransforms || {}) // user transforms
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export const TO_STRING = Symbol(__DEV__ ? `toString` : ``)
|
||||
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
|
||||
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
||||
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
||||
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
|
||||
|
||||
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
||||
// generated code. Make sure these are correctly exported in the runtime!
|
||||
@ -42,7 +43,8 @@ export const helperNameMap: any = {
|
||||
[TO_STRING]: `toString`,
|
||||
[MERGE_PROPS]: `mergeProps`,
|
||||
[TO_HANDLERS]: `toHandlers`,
|
||||
[CAMELIZE]: `camelize`
|
||||
[CAMELIZE]: `camelize`,
|
||||
[SET_BLOCK_TRACKING]: `setBlockTracking`
|
||||
}
|
||||
|
||||
export function registerRuntimeHelpers(helpers: any) {
|
||||
|
@ -100,7 +100,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
addIdentifiers(exp: ExpressionNode | string): void
|
||||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||
cache<T extends JSChildNode>(exp: T): CacheExpression | T
|
||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||
}
|
||||
|
||||
function createTransformContext(
|
||||
@ -219,8 +219,8 @@ function createTransformContext(
|
||||
true
|
||||
)
|
||||
},
|
||||
cache(exp) {
|
||||
return cacheHandlers ? createCacheExpression(++context.cached, exp) : exp
|
||||
cache(exp, isVNode = false) {
|
||||
return createCacheExpression(++context.cached, exp, isVNode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,12 +260,17 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
||||
const codegenNode = child.codegenNode as
|
||||
| ElementCodegenNode
|
||||
| ComponentCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
||||
| CacheExpression
|
||||
if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) {
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
||||
} else {
|
||||
codegenNode.callee = helper(CREATE_BLOCK)
|
||||
}
|
||||
root.codegenNode = createBlockExpression(codegenNode, context)
|
||||
} else {
|
||||
codegenNode.callee = helper(CREATE_BLOCK)
|
||||
root.codegenNode = codegenNode
|
||||
}
|
||||
root.codegenNode = createBlockExpression(codegenNode, context)
|
||||
} else {
|
||||
// - single <slot/>, IfNode, ForNode: already blocks.
|
||||
// - single text node: always patched.
|
||||
|
@ -4,12 +4,12 @@ import {
|
||||
TemplateChildNode,
|
||||
SimpleExpressionNode,
|
||||
ElementTypes,
|
||||
ElementCodegenNode,
|
||||
PlainElementNode,
|
||||
ComponentNode,
|
||||
TemplateNode,
|
||||
ElementNode,
|
||||
PlainElementCodegenNode
|
||||
PlainElementCodegenNode,
|
||||
CodegenNodeWithDirective
|
||||
} from '../ast'
|
||||
import { TransformContext } from '../transform'
|
||||
import { WITH_DIRECTIVES } from '../runtimeHelpers'
|
||||
@ -57,17 +57,20 @@ function walk(
|
||||
} else {
|
||||
// node may contain dynamic children, but its props may be eligible for
|
||||
// hoisting.
|
||||
const flag = getPatchFlag(child)
|
||||
if (
|
||||
(!flag ||
|
||||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
!hasDynamicKeyOrRef(child) &&
|
||||
!hasCachedProps(child)
|
||||
) {
|
||||
const props = getNodeProps(child)
|
||||
if (props && props !== `null`) {
|
||||
getVNodeCall(child).arguments[1] = context.hoist(props)
|
||||
const codegenNode = child.codegenNode!
|
||||
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
if (
|
||||
(!flag ||
|
||||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
!hasDynamicKeyOrRef(child) &&
|
||||
!hasCachedProps(child)
|
||||
) {
|
||||
const props = getNodeProps(child)
|
||||
if (props && props !== `null`) {
|
||||
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +103,11 @@ export function isStaticNode(
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
}
|
||||
const flag = getPatchFlag(node)
|
||||
const codegenNode = node.codegenNode!
|
||||
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
|
||||
return false
|
||||
}
|
||||
const flag = getPatchFlag(codegenNode)
|
||||
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
|
||||
// element self is static. check its children.
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
@ -165,26 +172,32 @@ function hasCachedProps(node: PlainElementNode): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
function getVNodeCall(node: PlainElementNode) {
|
||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode = codegenNode.arguments[0]
|
||||
function getNodeProps(node: PlainElementNode) {
|
||||
const codegenNode = node.codegenNode!
|
||||
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
||||
return getVNodeArgAt(
|
||||
codegenNode,
|
||||
1
|
||||
) as PlainElementCodegenNode['arguments'][1]
|
||||
}
|
||||
return codegenNode
|
||||
}
|
||||
|
||||
type NonCachedCodegenNode =
|
||||
| PlainElementCodegenNode
|
||||
| CodegenNodeWithDirective<PlainElementCodegenNode>
|
||||
|
||||
function getVNodeArgAt(
|
||||
node: PlainElementNode,
|
||||
node: NonCachedCodegenNode,
|
||||
index: number
|
||||
): PlainElementCodegenNode['arguments'][number] {
|
||||
return getVNodeCall(node).arguments[index]
|
||||
}
|
||||
|
||||
function getPatchFlag(node: PlainElementNode): number | undefined {
|
||||
function getVNodeCall(node: NonCachedCodegenNode) {
|
||||
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node
|
||||
}
|
||||
|
||||
function getPatchFlag(node: NonCachedCodegenNode): number | undefined {
|
||||
const flag = getVNodeArgAt(node, 3) as string
|
||||
return flag ? parseInt(flag, 10) : undefined
|
||||
}
|
||||
|
||||
function getNodeProps(node: PlainElementNode) {
|
||||
return getVNodeArgAt(node, 1) as PlainElementCodegenNode['arguments'][1]
|
||||
}
|
||||
|
@ -280,6 +280,11 @@ export function buildProps(
|
||||
continue
|
||||
}
|
||||
|
||||
// skip v-once - it is handled by its dedicated transform.
|
||||
if (name === 'once') {
|
||||
continue
|
||||
}
|
||||
|
||||
// special case for v-bind and v-on with no argument
|
||||
const isBind = name === 'bind'
|
||||
const isOn = name === 'on'
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
createObjectExpression,
|
||||
createObjectProperty,
|
||||
ForCodegenNode,
|
||||
ElementCodegenNode
|
||||
ElementCodegenNode,
|
||||
SlotOutletCodegenNode
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
@ -130,7 +131,7 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||
: null
|
||||
if (slotOutlet) {
|
||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||
childBlock = slotOutlet.codegenNode!
|
||||
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
|
||||
if (isTemplate && keyProperty) {
|
||||
// <template v-for="..." :key="..."><slot/></template>
|
||||
// we need to inject the key to the renderSlot() call.
|
||||
|
@ -69,6 +69,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
if (
|
||||
!__BROWSER__ &&
|
||||
context.prefixIdentifiers &&
|
||||
context.cacheHandlers &&
|
||||
!hasScopeRef(exp, context.identifiers)
|
||||
) {
|
||||
props[1].value = context.cache(props[1].value)
|
||||
|
@ -1,17 +1,15 @@
|
||||
import {
|
||||
DirectiveTransform,
|
||||
createObjectProperty,
|
||||
createSimpleExpression
|
||||
} from '@vue/compiler-core'
|
||||
import { NodeTransform } from '../transform'
|
||||
import { findDir } from '../utils'
|
||||
import { NodeTypes } from '../ast'
|
||||
import { SET_BLOCK_TRACKING } from '../runtimeHelpers'
|
||||
|
||||
export const transformOnce: DirectiveTransform = dir => {
|
||||
return {
|
||||
props: [
|
||||
createObjectProperty(
|
||||
createSimpleExpression(`$once`, true, dir.loc),
|
||||
createSimpleExpression('true', false)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
export const transformOnce: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) {
|
||||
context.helper(SET_BLOCK_TRACKING)
|
||||
return () => {
|
||||
if (node.codegenNode) {
|
||||
node.codegenNode = context.cache(node.codegenNode, true /* isVNode */)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export interface ComponentInternalInstance {
|
||||
accessCache: Data | null
|
||||
// cache for render function values that rely on _ctx but won't need updates
|
||||
// after initialized (e.g. inline handlers)
|
||||
renderCache: Function[] | null
|
||||
renderCache: (Function | VNode)[] | null
|
||||
|
||||
components: Record<string, Component>
|
||||
directives: Record<string, Directive>
|
||||
|
@ -179,14 +179,10 @@ export function createRenderer<
|
||||
optimized: boolean = false
|
||||
) {
|
||||
// patching & not same type, unmount old tree
|
||||
if (n1 != null) {
|
||||
if (!isSameType(n1, n2)) {
|
||||
anchor = getNextHostNode(n1)
|
||||
unmount(n1, parentComponent, parentSuspense, true)
|
||||
n1 = null
|
||||
} else if (n1.props && n1.props.$once) {
|
||||
return
|
||||
}
|
||||
if (n1 != null && !isSameType(n1, n2)) {
|
||||
anchor = getNextHostNode(n1)
|
||||
unmount(n1, parentComponent, parentSuspense, true)
|
||||
n1 = null
|
||||
}
|
||||
|
||||
const { type, shapeFlag } = n2
|
||||
|
@ -49,6 +49,7 @@ export { toString } from './helpers/toString'
|
||||
export { toHandlers } from './helpers/toHandlers'
|
||||
export { renderSlot } from './helpers/renderSlot'
|
||||
export { createSlots } from './helpers/createSlots'
|
||||
export { setBlockTracking } from './vnode'
|
||||
export { capitalize, camelize } from '@vue/shared'
|
||||
|
||||
// Internal, for integration with runtime compiler
|
||||
|
@ -105,7 +105,24 @@ export function openBlock(disableTracking?: boolean) {
|
||||
blockStack.push((currentBlock = disableTracking ? null : []))
|
||||
}
|
||||
|
||||
let shouldTrack = true
|
||||
// Whether we should be tracking dynamic child nodes inside a block.
|
||||
// Only tracks when this value is > 0
|
||||
// We are not using a simple boolean because this value may need to be
|
||||
// incremented/decremented by nested usage of v-once (see below)
|
||||
let shouldTrack = 1
|
||||
|
||||
// Block tracking sometimes needs to be disabled, for example during the
|
||||
// creation of a tree that needs to be cached by v-once. The compiler generates
|
||||
// code like this:
|
||||
// _cache[1] || (
|
||||
// setBlockTracking(-1),
|
||||
// _cache[1] = createVNode(...),
|
||||
// setBlockTracking(1),
|
||||
// _cache[1]
|
||||
// )
|
||||
export function setBlockTracking(value: number) {
|
||||
shouldTrack += value
|
||||
}
|
||||
|
||||
// Create a block root vnode. Takes the same exact arguments as `createVNode`.
|
||||
// A block root keeps track of dynamic nodes within the block in the
|
||||
@ -118,9 +135,9 @@ export function createBlock(
|
||||
dynamicProps?: string[]
|
||||
): VNode {
|
||||
// avoid a block with patchFlag tracking itself
|
||||
shouldTrack = false
|
||||
shouldTrack--
|
||||
const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
|
||||
shouldTrack = true
|
||||
shouldTrack++
|
||||
// save current block children on the block vnode
|
||||
vnode.dynamicChildren = currentBlock || EMPTY_ARR
|
||||
// close block
|
||||
@ -200,7 +217,7 @@ export function createVNode(
|
||||
// component doesn't need to update, it needs to persist the instance on to
|
||||
// the next vnode so that it can be properly unmounted later.
|
||||
if (
|
||||
shouldTrack &&
|
||||
shouldTrack > 0 &&
|
||||
currentBlock !== null &&
|
||||
(patchFlag > 0 ||
|
||||
shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
|
||||
|
@ -52,7 +52,7 @@ export const isPlainObject = (val: unknown): val is object =>
|
||||
toTypeString(val) === '[object Object]'
|
||||
|
||||
export const isReservedProp = (key: string): boolean =>
|
||||
key === 'key' || key === 'ref' || key === '$once' || key.startsWith(`onVnode`)
|
||||
key === 'key' || key === 'ref' || key.startsWith(`onVnode`)
|
||||
|
||||
const camelizeRE = /-(\w)/g
|
||||
export const camelize = (str: string): string => {
|
||||
|
Loading…
Reference in New Issue
Block a user