feat(compiler-core): more hoisting optimizations (#276)
This commit is contained in:
parent
9a37c4b2c3
commit
68a3879b88
@ -3986,6 +3986,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"content": "a < b",
|
"content": "a < b",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7151,6 +7152,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"content": "'</div>'",
|
"content": "'</div>'",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7320,6 +7322,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "se",
|
"content": "se",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7640,6 +7643,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"content": "",
|
"content": "",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7798,6 +7802,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "class",
|
"content": "class",
|
||||||
|
"isConstant": true,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7816,6 +7821,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ some: condition }",
|
"content": "{ some: condition }",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7876,6 +7882,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "style",
|
"content": "style",
|
||||||
|
"isConstant": true,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7894,6 +7901,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ color: 'red' }",
|
"content": "{ color: 'red' }",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7983,6 +7991,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "style",
|
"content": "style",
|
||||||
|
"isConstant": true,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -8001,6 +8010,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ color: 'red' }",
|
"content": "{ color: 'red' }",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -8080,6 +8090,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "class",
|
"content": "class",
|
||||||
|
"isConstant": true,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -8098,6 +8109,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ some: condition }",
|
"content": "{ some: condition }",
|
||||||
|
"isConstant": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
|
@ -298,6 +298,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `message`,
|
content: `message`,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 2, line: 1, column: 3 },
|
start: { offset: 2, line: 1, column: 3 },
|
||||||
end: { offset: 9, line: 1, column: 10 },
|
end: { offset: 9, line: 1, column: 10 },
|
||||||
@ -322,6 +323,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `a<b`,
|
content: `a<b`,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 3, line: 1, column: 4 },
|
start: { offset: 3, line: 1, column: 4 },
|
||||||
end: { offset: 6, line: 1, column: 7 },
|
end: { offset: 6, line: 1, column: 7 },
|
||||||
@ -347,6 +349,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `a<b`,
|
content: `a<b`,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 3, line: 1, column: 4 },
|
start: { offset: 3, line: 1, column: 4 },
|
||||||
end: { offset: 6, line: 1, column: 7 },
|
end: { offset: 6, line: 1, column: 7 },
|
||||||
@ -365,6 +368,7 @@ describe('compiler: parse', () => {
|
|||||||
content: {
|
content: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
content: 'c>d',
|
content: 'c>d',
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 12, line: 1, column: 13 },
|
start: { offset: 12, line: 1, column: 13 },
|
||||||
@ -390,6 +394,8 @@ describe('compiler: parse', () => {
|
|||||||
content: {
|
content: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
// The `isConstant` is the default value and will be determined in `transformExpression`.
|
||||||
|
isConstant: false,
|
||||||
content: '"</div>"',
|
content: '"</div>"',
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
@ -974,6 +980,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 11, line: 1, column: 12 },
|
start: { offset: 11, line: 1, column: 12 },
|
||||||
end: { offset: 12, line: 1, column: 13 },
|
end: { offset: 12, line: 1, column: 13 },
|
||||||
@ -999,6 +1006,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'click',
|
content: 'click',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'click',
|
source: 'click',
|
||||||
@ -1071,6 +1079,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'click',
|
content: 'click',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'click',
|
source: 'click',
|
||||||
@ -1107,6 +1116,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
@ -1127,6 +1137,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
@ -1153,6 +1164,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
@ -1173,6 +1185,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 13, line: 1, column: 14 },
|
start: { offset: 13, line: 1, column: 14 },
|
||||||
@ -1199,6 +1212,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
@ -1219,6 +1233,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
@ -1245,6 +1260,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
@ -1265,6 +1281,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
|
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 14, line: 1, column: 15 },
|
start: { offset: 14, line: 1, column: 15 },
|
||||||
@ -1291,6 +1308,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
start: {
|
start: {
|
||||||
@ -1310,6 +1328,8 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: '{ b }',
|
content: '{ b }',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
// The `isConstant` is the default value and will be determined in transformExpression
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 10, line: 1, column: 11 },
|
start: { offset: 10, line: 1, column: 11 },
|
||||||
end: { offset: 15, line: 1, column: 16 },
|
end: { offset: 15, line: 1, column: 16 },
|
||||||
|
@ -152,6 +152,93 @@ return function render() {
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
const _createVNode = Vue.createVNode
|
||||||
|
|
||||||
|
const _hoisted_1 = { class: { foo: true }}
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */)
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
const _createVNode = Vue.createVNode
|
||||||
|
|
||||||
|
const _hoisted_1 = _createVNode(\\"span\\", null, [\\"foo \\", _toString(1), _toString(2)])
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_hoisted_1
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
const _createVNode = Vue.createVNode
|
||||||
|
|
||||||
|
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1), 1 /* TEXT */)
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_hoisted_1
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable (2) 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
(_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
||||||
|
return (_openBlock(), _createBlock(\\"p\\", null, [
|
||||||
|
_createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
|
||||||
|
]))
|
||||||
|
}), 128 /* UNKEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render() {
|
||||||
|
with (this) {
|
||||||
|
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
(_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
||||||
|
return (_openBlock(), _createBlock(\\"p\\", null, [
|
||||||
|
_createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
|
||||||
|
]))
|
||||||
|
}), 128 /* UNKEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { parse, transform, NodeTypes, generate } from '../../src'
|
import {
|
||||||
|
parse,
|
||||||
|
transform,
|
||||||
|
NodeTypes,
|
||||||
|
generate,
|
||||||
|
CompilerOptions
|
||||||
|
} from '../../src'
|
||||||
import {
|
import {
|
||||||
OPEN_BLOCK,
|
OPEN_BLOCK,
|
||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
@ -8,17 +14,24 @@ import {
|
|||||||
RENDER_LIST
|
RENDER_LIST
|
||||||
} from '../../src/runtimeHelpers'
|
} from '../../src/runtimeHelpers'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformFor } from '../../src/transforms/vFor'
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
function transformWithHoist(template: string) {
|
function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
||||||
const ast = parse(template)
|
const ast = parse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
nodeTransforms: [transformIf, transformFor, transformElement],
|
prefixIdentifiers: options.prefixIdentifiers,
|
||||||
|
nodeTransforms: [
|
||||||
|
transformIf,
|
||||||
|
transformFor,
|
||||||
|
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||||
|
transformElement
|
||||||
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
bind: transformBind
|
bind: transformBind
|
||||||
}
|
}
|
||||||
@ -429,4 +442,186 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('prefixIdentifiers', () => {
|
||||||
|
test('hoist nested static tree with static interpolation', () => {
|
||||||
|
const { root, args } = transformWithHoist(
|
||||||
|
`<div><span>foo {{ 1 }} {{ 2 }}</span></div>`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(root.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_VNODE,
|
||||||
|
arguments: [
|
||||||
|
`"span"`,
|
||||||
|
`null`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: `foo `
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `1`,
|
||||||
|
isStatic: false,
|
||||||
|
isConstant: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `2`,
|
||||||
|
isStatic: false,
|
||||||
|
isConstant: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(args).toMatchObject([
|
||||||
|
`"div"`,
|
||||||
|
`null`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
codegenNode: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `_hoisted_1`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hoist nested static tree with static prop value', () => {
|
||||||
|
const { root, args } = transformWithHoist(
|
||||||
|
`<div><span :foo="0">{{ 1 }}</span></div>`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(root.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_VNODE,
|
||||||
|
arguments: [
|
||||||
|
`"span"`,
|
||||||
|
createObjectMatcher({ foo: `[0]` }),
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `1`,
|
||||||
|
isStatic: false,
|
||||||
|
isConstant: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'1 /* TEXT */'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(args).toMatchObject([
|
||||||
|
`"div"`,
|
||||||
|
`null`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
codegenNode: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `_hoisted_1`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hoist class with static object value', () => {
|
||||||
|
const { root, args } = transformWithHoist(
|
||||||
|
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(root.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `class`,
|
||||||
|
isConstant: true,
|
||||||
|
isStatic: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `{ foo: true }`,
|
||||||
|
isConstant: true,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(args).toMatchObject([
|
||||||
|
`"div"`,
|
||||||
|
`null`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
codegenNode: {
|
||||||
|
callee: CREATE_VNODE,
|
||||||
|
arguments: [
|
||||||
|
`"span"`,
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `_hoisted_1`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`,
|
||||||
|
isConstant: false,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`1 /* TEXT */`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should NOT hoist expressions that with scope variable', () => {
|
||||||
|
const { root } = transformWithHoist(
|
||||||
|
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(root.hoists.length).toBe(0)
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should NOT hoist expressions that with scope variable (2)', () => {
|
||||||
|
const { root } = transformWithHoist(
|
||||||
|
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(root.hoists.length).toBe(0)
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -79,6 +79,7 @@ describe('isEmptyExpression', () => {
|
|||||||
content: '',
|
content: '',
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
loc: null as any
|
loc: null as any
|
||||||
})
|
})
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
@ -90,6 +91,7 @@ describe('isEmptyExpression', () => {
|
|||||||
content: ' \t ',
|
content: ' \t ',
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
loc: null as any
|
loc: null as any
|
||||||
})
|
})
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
@ -101,6 +103,7 @@ describe('isEmptyExpression', () => {
|
|||||||
content: 'foo',
|
content: 'foo',
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isConstant: true,
|
||||||
loc: null as any
|
loc: null as any
|
||||||
})
|
})
|
||||||
).toBe(false)
|
).toBe(false)
|
||||||
|
@ -172,6 +172,7 @@ export interface SimpleExpressionNode extends Node {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION
|
type: NodeTypes.SIMPLE_EXPRESSION
|
||||||
content: string
|
content: string
|
||||||
isStatic: boolean
|
isStatic: boolean
|
||||||
|
isConstant: boolean
|
||||||
// an expression parsed as the params of a function will track
|
// an expression parsed as the params of a function will track
|
||||||
// the identifiers declared inside the function body.
|
// the identifiers declared inside the function body.
|
||||||
identifiers?: string[]
|
identifiers?: string[]
|
||||||
@ -501,11 +502,13 @@ export function createObjectProperty(
|
|||||||
export function createSimpleExpression(
|
export function createSimpleExpression(
|
||||||
content: SimpleExpressionNode['content'],
|
content: SimpleExpressionNode['content'],
|
||||||
isStatic: SimpleExpressionNode['isStatic'],
|
isStatic: SimpleExpressionNode['isStatic'],
|
||||||
loc: SourceLocation = locStub
|
loc: SourceLocation = locStub,
|
||||||
|
isConstant: boolean = false
|
||||||
): SimpleExpressionNode {
|
): SimpleExpressionNode {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
|
isConstant,
|
||||||
content,
|
content,
|
||||||
isStatic
|
isStatic
|
||||||
}
|
}
|
||||||
|
@ -564,9 +564,12 @@ function parseAttribute(
|
|||||||
)
|
)
|
||||||
let content = match[2]
|
let content = match[2]
|
||||||
let isStatic = true
|
let isStatic = true
|
||||||
|
// Non-dynamic arg is a constant.
|
||||||
|
let isConstant = true
|
||||||
|
|
||||||
if (content.startsWith('[')) {
|
if (content.startsWith('[')) {
|
||||||
isStatic = false
|
isStatic = false
|
||||||
|
isConstant = false
|
||||||
|
|
||||||
if (!content.endsWith(']')) {
|
if (!content.endsWith(']')) {
|
||||||
emitError(
|
emitError(
|
||||||
@ -582,6 +585,7 @@ function parseAttribute(
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content,
|
content,
|
||||||
isStatic,
|
isStatic,
|
||||||
|
isConstant,
|
||||||
loc
|
loc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,6 +611,8 @@ function parseAttribute(
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: value.content,
|
content: value.content,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
// Set `isConstant` to false by default and will decide in transformExpression
|
||||||
|
isConstant: false,
|
||||||
loc: value.loc
|
loc: value.loc
|
||||||
},
|
},
|
||||||
arg,
|
arg,
|
||||||
@ -713,6 +719,8 @@ function parseInterpolation(
|
|||||||
content: {
|
content: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
// Set `isConstant` to false by default and will decide in transformExpression
|
||||||
|
isConstant: false,
|
||||||
content,
|
content,
|
||||||
loc: getSelection(context, innerStart, innerEnd)
|
loc: getSelection(context, innerStart, innerEnd)
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
RootNode,
|
RootNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
|
SimpleExpressionNode,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
ElementCodegenNode,
|
ElementCodegenNode,
|
||||||
PlainElementNode,
|
PlainElementNode,
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext } from '../transform'
|
||||||
import { APPLY_DIRECTIVES } from '../runtimeHelpers'
|
import { APPLY_DIRECTIVES } from '../runtimeHelpers'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags, isString, isSymbol } from '@vue/shared'
|
||||||
import { isSlotOutlet, findProp } from '../utils'
|
import { isSlotOutlet, findProp } from '../utils'
|
||||||
|
|
||||||
function hasDynamicKey(node: ElementNode) {
|
function hasDynamicKey(node: ElementNode) {
|
||||||
@ -107,7 +108,7 @@ function getPatchFlag(node: PlainElementNode): number | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isStaticNode(
|
function isStaticNode(
|
||||||
node: TemplateChildNode,
|
node: TemplateChildNode | SimpleExpressionNode,
|
||||||
resultCache: Map<TemplateChildNode, boolean>
|
resultCache: Map<TemplateChildNode, boolean>
|
||||||
): boolean {
|
): boolean {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@ -119,7 +120,7 @@ function isStaticNode(
|
|||||||
return resultCache.get(node) as boolean
|
return resultCache.get(node) as boolean
|
||||||
}
|
}
|
||||||
const flag = getPatchFlag(node)
|
const flag = getPatchFlag(node)
|
||||||
if (!flag) {
|
if (!flag || flag === PatchFlags.TEXT) {
|
||||||
// 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++) {
|
||||||
if (!isStaticNode(node.children[i], resultCache)) {
|
if (!isStaticNode(node.children[i], resultCache)) {
|
||||||
@ -137,9 +138,17 @@ function isStaticNode(
|
|||||||
return true
|
return true
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
case NodeTypes.INTERPOLATION:
|
|
||||||
case NodeTypes.COMPOUND_EXPRESSION:
|
|
||||||
return false
|
return false
|
||||||
|
case NodeTypes.INTERPOLATION:
|
||||||
|
return isStaticNode(node.content, resultCache)
|
||||||
|
case NodeTypes.SIMPLE_EXPRESSION:
|
||||||
|
return node.isConstant
|
||||||
|
case NodeTypes.COMPOUND_EXPRESSION:
|
||||||
|
return node.children.every(child => {
|
||||||
|
return (
|
||||||
|
isString(child) || isSymbol(child) || isStaticNode(child, resultCache)
|
||||||
|
)
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const exhaustiveCheck: never = node
|
const exhaustiveCheck: never = node
|
||||||
|
@ -190,7 +190,12 @@ export function buildProps(
|
|||||||
|
|
||||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||||
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
||||||
if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) {
|
if (
|
||||||
|
value.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
||||||
|
// E.g: <p :foo="1 + 2" />.
|
||||||
|
// Do not add prop `foo` to `dynamicPropNames`.
|
||||||
|
(!value.isStatic && !value.isConstant)
|
||||||
|
) {
|
||||||
const name = key.content
|
const name = key.content
|
||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
|
@ -61,6 +61,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
|
|
||||||
interface PrefixMeta {
|
interface PrefixMeta {
|
||||||
prefix?: string
|
prefix?: string
|
||||||
|
isConstant: boolean
|
||||||
start: number
|
start: number
|
||||||
end: number
|
end: number
|
||||||
scopeIds?: Set<string>
|
scopeIds?: Set<string>
|
||||||
@ -108,6 +109,7 @@ export function processExpression(
|
|||||||
const ids: (Identifier & PrefixMeta)[] = []
|
const ids: (Identifier & PrefixMeta)[] = []
|
||||||
const knownIds = Object.create(context.identifiers)
|
const knownIds = Object.create(context.identifiers)
|
||||||
|
|
||||||
|
let isConstant = true
|
||||||
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
||||||
walkJS(ast, {
|
walkJS(ast, {
|
||||||
enter(node: Node & PrefixMeta, parent) {
|
enter(node: Node & PrefixMeta, parent) {
|
||||||
@ -120,8 +122,15 @@ export function processExpression(
|
|||||||
node.prefix = `${node.name}: `
|
node.prefix = `${node.name}: `
|
||||||
}
|
}
|
||||||
node.name = `_ctx.${node.name}`
|
node.name = `_ctx.${node.name}`
|
||||||
|
node.isConstant = false
|
||||||
|
isConstant = false
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
} else if (!isStaticPropertyKey(node, parent)) {
|
} else if (!isStaticPropertyKey(node, parent)) {
|
||||||
|
// This means this identifier is pointing to a scope variable (a v-for alias, or a v-slot prop)
|
||||||
|
// which is also dynamic and cannot be hoisted.
|
||||||
|
node.isConstant = !(
|
||||||
|
knownIds[node.name] && shouldPrefix(node, parent)
|
||||||
|
)
|
||||||
// also generate sub-expressions for other identifiers for better
|
// also generate sub-expressions for other identifiers for better
|
||||||
// source map support. (except for property keys which are static)
|
// source map support. (except for property keys which are static)
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
@ -190,11 +199,16 @@ export function processExpression(
|
|||||||
}
|
}
|
||||||
const source = rawExp.slice(start, end)
|
const source = rawExp.slice(start, end)
|
||||||
children.push(
|
children.push(
|
||||||
createSimpleExpression(id.name, false, {
|
createSimpleExpression(
|
||||||
|
id.name,
|
||||||
|
false,
|
||||||
|
{
|
||||||
source,
|
source,
|
||||||
start: advancePositionWithClone(node.loc.start, source, start),
|
start: advancePositionWithClone(node.loc.start, source, start),
|
||||||
end: advancePositionWithClone(node.loc.start, source, end)
|
end: advancePositionWithClone(node.loc.start, source, end)
|
||||||
})
|
},
|
||||||
|
id.isConstant /* isConstant */
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if (i === ids.length - 1 && end < rawExp.length) {
|
if (i === ids.length - 1 && end < rawExp.length) {
|
||||||
children.push(rawExp.slice(end))
|
children.push(rawExp.slice(end))
|
||||||
@ -206,6 +220,7 @@ export function processExpression(
|
|||||||
ret = createCompoundExpression(children, node.loc)
|
ret = createCompoundExpression(children, node.loc)
|
||||||
} else {
|
} else {
|
||||||
ret = node
|
ret = node
|
||||||
|
ret.isConstant = isConstant
|
||||||
}
|
}
|
||||||
ret.identifiers = Object.keys(knownIds)
|
ret.identifiers = Object.keys(knownIds)
|
||||||
return ret
|
return ret
|
||||||
|
@ -117,6 +117,7 @@ describe('DOM parser', () => {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `a < b`,
|
content: `a < b`,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isConstant: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
end: { offset: 16, line: 1, column: 17 },
|
end: { offset: 16, line: 1, column: 17 },
|
||||||
|
Loading…
Reference in New Issue
Block a user