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