wip(compiler): generate blocks for v-if

This commit is contained in:
Evan You 2019-10-01 12:25:13 -04:00
parent e31fb3c172
commit 5de744d4e1
21 changed files with 359 additions and 354 deletions

View File

@ -105,9 +105,11 @@ return function render() {
exports[`compiler: codegen function mode preamble 1`] = ` exports[`compiler: codegen function mode preamble 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue
return null return null
} }
}" }"
@ -137,24 +139,7 @@ exports[`compiler: codegen ifNode 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return foo return (foo, bar)
? \\"foo\\"
: (a + b)
? _toString(bye)
: _createVNode(_Comment, 0, \\"foo\\")
}
}"
`;
exports[`compiler: codegen ifNode with no v-else 1`] = `
"
return function render() {
with (this) {
return foo
? \\"foo\\"
: (a + b)
? _toString(bye)
: null
} }
}" }"
`; `;

View File

@ -2,17 +2,27 @@
exports[`compiler: integration tests function mode 1`] = ` exports[`compiler: integration tests function mode 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, applyDirectives: _applyDirectives, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue
return _createVNode(\\"div\\", { return _createVNode(\\"div\\", {
id: \\"foo\\", id: \\"foo\\",
class: bar.baz class: bar.baz
}, [ }, [
_toString(world.burn()), _toString(world.burn()),
ok (_openBlock(), ok
? _createVNode(\\"div\\", null, \\"yes\\") ? _createBlock(
: \\"no\\", \\"div\\",
{ key: 0 },
\\"yes\\"
)
: _createBlock(
_Fragment,
{ key: 1 },
\\"no\\"
)),
_renderList(list, (value, index) => { _renderList(list, (value, index) => {
return _createVNode(\\"div\\", null, [ return _createVNode(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index)) _createVNode(\\"span\\", null, _toString(value + index))
@ -24,7 +34,7 @@ return function render() {
`; `;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
"const { createVNode, toString, renderList } = Vue "const { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -33,9 +43,17 @@ return function render() {
class: _ctx.bar.baz class: _ctx.bar.baz
}, [ }, [
toString(_ctx.world.burn()), toString(_ctx.world.burn()),
(_ctx.ok) (openBlock(), (_ctx.ok)
? createVNode(\\"div\\", null, \\"yes\\") ? createBlock(
: \\"no\\", \\"div\\",
{ key: 0 },
\\"yes\\"
)
: createBlock(
Fragment,
{ key: 1 },
\\"no\\"
)),
renderList(_ctx.list, (value, index) => { renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return createVNode(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index)) createVNode(\\"span\\", null, toString(value + index))
@ -46,7 +64,7 @@ return function render() {
`; `;
exports[`compiler: integration tests module mode 1`] = ` exports[`compiler: integration tests module mode 1`] = `
"import { createVNode, toString, renderList } from \\"vue\\" "import { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } from \\"vue\\"
export default function render() { export default function render() {
const _ctx = this const _ctx = this
@ -55,9 +73,17 @@ export default function render() {
class: _ctx.bar.baz class: _ctx.bar.baz
}, [ }, [
_toString(_ctx.world.burn()), _toString(_ctx.world.burn()),
(_ctx.ok) (openBlock(), (_ctx.ok)
? createVNode(\\"div\\", null, \\"yes\\") ? createBlock(
: \\"no\\", \\"div\\",
{ key: 0 },
\\"yes\\"
)
: createBlock(
Fragment,
{ key: 1 },
\\"no\\"
)),
_renderList(_ctx.list, (value, index) => { _renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return createVNode(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index)) createVNode(\\"span\\", null, _toString(value + index))

View File

@ -12,7 +12,8 @@ import {
createArrayExpression, createArrayExpression,
ElementNode, ElementNode,
createCompoundExpression, createCompoundExpression,
createInterpolation createInterpolation,
createSequenceExpression
} from '../src' } from '../src'
import { import {
CREATE_VNODE, CREATE_VNODE,
@ -100,8 +101,7 @@ describe('compiler: codegen', () => {
[ [
createObjectProperty( createObjectProperty(
createSimpleExpression(`id`, true, mockLoc), createSimpleExpression(`id`, true, mockLoc),
createSimpleExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc)
mockLoc
) )
], ],
mockLoc mockLoc
@ -226,8 +226,7 @@ describe('compiler: codegen', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({
children: [ children: [
createCompoundExpression( createCompoundExpression([
[
`_ctx.`, `_ctx.`,
createSimpleExpression(`foo`, false, mockLoc), createSimpleExpression(`foo`, false, mockLoc),
` + `, ` + `,
@ -236,9 +235,7 @@ describe('compiler: codegen', () => {
loc: mockLoc, loc: mockLoc,
content: createSimpleExpression(`bar`, false, mockLoc) content: createSimpleExpression(`bar`, false, mockLoc)
} }
], ])
mockLoc
)
] ]
}) })
) )
@ -253,90 +250,16 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.IF, type: NodeTypes.IF,
loc: mockLoc, loc: mockLoc,
branches: [ branches: [],
{ codegenNode: createSequenceExpression([
type: NodeTypes.IF_BRANCH, createSimpleExpression('foo', false),
condition: createSimpleExpression('foo', false, mockLoc), createSimpleExpression('bar', false)
loc: mockLoc, ])
children: [
{
type: NodeTypes.TEXT,
content: 'foo',
isEmpty: false,
loc: mockLoc
}
]
},
{
type: NodeTypes.IF_BRANCH,
condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc,
children: [createInterpolation(`bye`, mockLoc)]
},
{
type: NodeTypes.IF_BRANCH,
condition: undefined,
loc: mockLoc,
children: [
{
type: NodeTypes.COMMENT,
content: 'foo',
loc: mockLoc
}
]
}
]
} }
] ]
}) })
) )
expect(code).toMatch(` expect(code).toMatch(`return (foo, bar)`)
return foo
? "foo"
: (a + b)
? _${TO_STRING}(bye)
: _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
expect(code).toMatchSnapshot()
})
test('ifNode with no v-else', () => {
const { code } = generate(
createRoot({
children: [
{
type: NodeTypes.IF,
loc: mockLoc,
branches: [
{
type: NodeTypes.IF_BRANCH,
condition: createSimpleExpression('foo', false, mockLoc),
loc: mockLoc,
children: [
{
type: NodeTypes.TEXT,
content: 'foo',
isEmpty: false,
loc: mockLoc
}
]
},
{
type: NodeTypes.IF_BRANCH,
condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc,
children: [createInterpolation(`bye`, mockLoc)]
}
]
}
]
})
)
expect(code).toMatch(`
return foo
? "foo"
: (a + b)
? _${TO_STRING}(bye)
: null`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -570,13 +493,11 @@ describe('compiler: codegen', () => {
[ [
createObjectProperty( createObjectProperty(
createSimpleExpression(`id`, true, mockLoc), createSimpleExpression(`id`, true, mockLoc),
createSimpleExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc)
mockLoc
), ),
createObjectProperty( createObjectProperty(
createSimpleExpression(`prop`, false, mockLoc), createSimpleExpression(`prop`, false, mockLoc),
createSimpleExpression(`bar`, false, mockLoc), createSimpleExpression(`bar`, false, mockLoc)
mockLoc
), ),
// compound expression as computed key // compound expression as computed key
createObjectProperty( createObjectProperty(
@ -588,8 +509,7 @@ describe('compiler: codegen', () => {
createSimpleExpression(`bar`, false, mockLoc) createSimpleExpression(`bar`, false, mockLoc)
] ]
}, },
createSimpleExpression(`bar`, false, mockLoc), createSimpleExpression(`bar`, false, mockLoc)
mockLoc
) )
], ],
mockLoc mockLoc
@ -603,8 +523,7 @@ describe('compiler: codegen', () => {
createObjectProperty( createObjectProperty(
// should quote the key! // should quote the key!
createSimpleExpression(`some-key`, true, mockLoc), createSimpleExpression(`some-key`, true, mockLoc),
createSimpleExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc)
mockLoc
) )
], ],
mockLoc mockLoc
@ -641,4 +560,8 @@ describe('compiler: codegen', () => {
])`) ])`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test.todo('SequenceExpression')
test.todo('ConditionalExpression')
}) })

View File

@ -50,10 +50,6 @@ describe('compiler: integration tests', () => {
filename: `foo.vue` filename: `foo.vue`
}) })
expect(code).toMatch(
`const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source]) expect(map!.sourcesContent).toEqual([source])
@ -120,8 +116,6 @@ describe('compiler: integration tests', () => {
prefixIdentifiers: true prefixIdentifiers: true
}) })
expect(code).toMatch(`const { createVNode, toString, renderList } = Vue`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source]) expect(map!.sourcesContent).toEqual([source])
@ -197,10 +191,6 @@ describe('compiler: integration tests', () => {
filename: `foo.vue` filename: `foo.vue`
}) })
expect(code).toMatch(
`import { createVNode, toString, renderList } from "vue"`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source]) expect(map!.sourcesContent).toEqual([source])

View File

@ -2,9 +2,11 @@
exports[`compiler: optimize interpolation consecutive text 1`] = ` exports[`compiler: optimize interpolation consecutive text 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { toString: _toString } = _Vue const { toString: _toString } = _Vue
return _toString(foo) + \\" bar \\" + _toString(baz) return _toString(foo) + \\" bar \\" + _toString(baz)
} }
}" }"
@ -12,9 +14,11 @@ return function render() {
exports[`compiler: optimize interpolation consecutive text between elements 1`] = ` exports[`compiler: optimize interpolation consecutive text between elements 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, toString: _toString } = _Vue const { createVNode: _createVNode, toString: _toString } = _Vue
return [ return [
_createVNode(\\"div\\"), _createVNode(\\"div\\"),
_toString(foo) + \\" bar \\" + _toString(baz), _toString(foo) + \\" bar \\" + _toString(baz),
@ -26,9 +30,11 @@ return function render() {
exports[`compiler: optimize interpolation consecutive text mixed with elements 1`] = ` exports[`compiler: optimize interpolation consecutive text mixed with elements 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, toString: _toString } = _Vue const { createVNode: _createVNode, toString: _toString } = _Vue
return [ return [
_createVNode(\\"div\\"), _createVNode(\\"div\\"),
_toString(foo) + \\" bar \\" + _toString(baz), _toString(foo) + \\" bar \\" + _toString(baz),
@ -42,9 +48,11 @@ return function render() {
exports[`compiler: optimize interpolation no consecutive text 1`] = ` exports[`compiler: optimize interpolation no consecutive text 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { toString: _toString } = _Vue const { toString: _toString } = _Vue
return _toString(foo) return _toString(foo)
} }
}" }"

View File

@ -74,42 +74,25 @@ describe('compiler: element transform', () => {
}) })
test('static props', () => { test('static props', () => {
const { root, node } = parseWithElementTransform( const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
`<div id="foo" class="bar" />`
)
expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.callee).toBe(`_${CREATE_VNODE}`)
// should hoist the static object expect(node.arguments).toMatchObject([
expect(root.hoists).toMatchObject([ `"div"`,
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo', id: 'foo',
class: 'bar' class: 'bar'
}) })
]) ])
expect(node.arguments).toMatchObject([
`"div"`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
])
}) })
test('props + children', () => { test('props + children', () => {
const { root, node } = parseWithElementTransform( const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
`<div id="foo"><span/></div>`
)
expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(root.hoists).toMatchObject([
createStaticObjectMatcher({
id: 'foo'
})
])
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
{ createStaticObjectMatcher({
type: NodeTypes.SIMPLE_EXPRESSION, id: 'foo'
content: `_hoisted_1` }),
},
[ [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
@ -298,7 +281,7 @@ describe('compiler: element transform', () => {
foo(dir) { foo(dir) {
_dir = dir _dir = dir
return { return {
props: createObjectProperty(dir.arg!, dir.exp!, dir.loc), props: createObjectProperty(dir.arg!, dir.exp!),
needRuntime: false needRuntime: false
} }
} }
@ -326,7 +309,7 @@ describe('compiler: element transform', () => {
foo(dir) { foo(dir) {
_dir = dir _dir = dir
return { return {
props: [createObjectProperty(dir.arg!, dir.exp!, dir.loc)], props: [createObjectProperty(dir.arg!, dir.exp!)],
needRuntime: true needRuntime: true
} }
} }

View File

@ -5,7 +5,6 @@ import {
DirectiveNode, DirectiveNode,
NodeTypes, NodeTypes,
CompilerOptions, CompilerOptions,
IfNode,
InterpolationNode InterpolationNode
} from '../../src' } from '../../src'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
@ -159,14 +158,6 @@ describe('compiler: expression transform', () => {
}) })
}) })
test('should prefix v-if condition', () => {
const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode
expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok`
})
})
test('should not prefix whitelisted globals', () => { test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ Math.max(1, 2) }}` `{{ Math.max(1, 2) }}`

View File

@ -61,16 +61,6 @@ describe('compiler: transform v-bind', () => {
column: 19 column: 19
} }
} }
},
loc: {
start: {
line: 1,
column: 6
},
end: {
line: 1,
column: 20
}
} }
}) })
}) })

View File

@ -1,6 +1,7 @@
import { parse } from '../../src/parse' import { parse } from '../../src/parse'
import { transform } from '../../src/transform' import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement'
import { import {
IfNode, IfNode,
NodeTypes, NodeTypes,
@ -18,7 +19,10 @@ function parseWithIfTransform(
returnIndex: number = 0 returnIndex: number = 0
): IfNode { ): IfNode {
const node = parse(template, options) const node = parse(template, options)
transform(node, { nodeTransforms: [transformIf], ...options }) transform(node, {
nodeTransforms: [transformIf, transformElement],
...options
})
if (!options.onError) { if (!options.onError) {
expect(node.children.length).toBe(1) expect(node.children.length).toBe(1)
expect(node.children[0].type).toBe(NodeTypes.IF) expect(node.children[0].type).toBe(NodeTypes.IF)
@ -153,6 +157,21 @@ describe('compiler: transform v-if', () => {
expect((b3.children[1] as TextNode).content).toBe(`fine`) expect((b3.children[1] as TextNode).content).toBe(`fine`)
}) })
test('should prefix v-if condition', () => {
const node = parseWithIfTransform(`<div v-if="ok"/>`, {
prefixIdentifiers: true
}) as IfNode
expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok`
})
})
describe('codegen', () => {
// TODO
})
describe('errors', () => {
test('error on v-else missing adjacent v-if', () => { test('error on v-else missing adjacent v-if', () => {
const onError = jest.fn() const onError = jest.fn()
@ -172,7 +191,11 @@ describe('compiler: transform v-if', () => {
} }
]) ])
const node3 = parseWithIfTransform(`<div/>foo<div v-else/>`, { onError }, 2) const node3 = parseWithIfTransform(
`<div/>foo<div v-else/>`,
{ onError },
2
)
expect(onError.mock.calls[2]).toMatchObject([ expect(onError.mock.calls[2]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
@ -216,4 +239,5 @@ describe('compiler: transform v-if', () => {
} }
]) ])
}) })
})
}) })

View File

@ -58,16 +58,6 @@ describe('compiler: transform v-on', () => {
column: 25 column: 25
} }
} }
},
loc: {
start: {
line: 1,
column: 6
},
end: {
line: 1,
column: 26
}
} }
}) })
}) })

View File

@ -142,7 +142,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node { export interface IfNode extends Node {
type: NodeTypes.IF type: NodeTypes.IF
branches: IfBranchNode[] branches: IfBranchNode[]
codegenNode: JSChildNode | undefined codegenNode: SequenceExpression
} }
export interface IfBranchNode extends Node { export interface IfBranchNode extends Node {
@ -212,9 +212,20 @@ export interface ConditionalExpression extends Node {
alternate: JSChildNode alternate: JSChildNode
} }
// AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never
// associated with template nodes, so their source locations are just a stub.
// Container types like CompoundExpression also don't need a real location.
const locStub: SourceLocation = {
source: '',
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }
}
export function createArrayExpression( export function createArrayExpression(
elements: ArrayExpression['elements'], elements: ArrayExpression['elements'],
loc: SourceLocation loc: SourceLocation = locStub
): ArrayExpression { ): ArrayExpression {
return { return {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
@ -225,7 +236,7 @@ export function createArrayExpression(
export function createObjectExpression( export function createObjectExpression(
properties: Property[], properties: Property[],
loc: SourceLocation loc: SourceLocation = locStub
): ObjectExpression { ): ObjectExpression {
return { return {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
@ -236,12 +247,11 @@ export function createObjectExpression(
export function createObjectProperty( export function createObjectProperty(
key: ExpressionNode, key: ExpressionNode,
value: JSChildNode, value: JSChildNode
loc: SourceLocation
): Property { ): Property {
return { return {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
loc, loc: locStub,
key, key,
value value
} }
@ -250,7 +260,7 @@ export function createObjectProperty(
export function createSimpleExpression( export function createSimpleExpression(
content: string, content: string,
isStatic: boolean, isStatic: boolean,
loc: SourceLocation loc: SourceLocation = locStub
): SimpleExpressionNode { ): SimpleExpressionNode {
return { return {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
@ -274,20 +284,19 @@ export function createInterpolation(
} }
export function createCompoundExpression( export function createCompoundExpression(
children: CompoundExpressionNode['children'], children: CompoundExpressionNode['children']
loc: SourceLocation
): CompoundExpressionNode { ): CompoundExpressionNode {
return { return {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
loc, loc: locStub,
children children
} }
} }
export function createCallExpression( export function createCallExpression(
callee: string, callee: string,
args: CallExpression['arguments'], args: CallExpression['arguments'] = [],
loc: SourceLocation loc: SourceLocation = locStub
): CallExpression { ): CallExpression {
return { return {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -300,7 +309,7 @@ export function createCallExpression(
export function createFunctionExpression( export function createFunctionExpression(
params: ExpressionNode | undefined, params: ExpressionNode | undefined,
returns: TemplateChildNode[], returns: TemplateChildNode[],
loc: SourceLocation loc: SourceLocation = locStub
): SlotFunctionExpression { ): SlotFunctionExpression {
return { return {
type: NodeTypes.JS_SLOT_FUNCTION, type: NodeTypes.JS_SLOT_FUNCTION,
@ -310,15 +319,9 @@ export function createFunctionExpression(
} }
} }
// sequence and conditional expressions are never associated with template nodes, export function createSequenceExpression(
// so their source locations are just a stub. expressions: JSChildNode[]
const locStub: SourceLocation = { ): SequenceExpression {
source: '',
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }
}
export function createSequenceExpression(expressions: JSChildNode[]) {
return { return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION, type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions, expressions,
@ -330,7 +333,7 @@ export function createConditionalExpression(
test: ExpressionNode, test: ExpressionNode,
consequent: JSChildNode, consequent: JSChildNode,
alternate: JSChildNode alternate: JSChildNode
) { ): ConditionalExpression {
return { return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test, test,

View File

@ -12,7 +12,6 @@ import {
CallExpression, CallExpression,
ArrayExpression, ArrayExpression,
ObjectExpression, ObjectExpression,
IfBranchNode,
SourceLocation, SourceLocation,
Position, Position,
InterpolationNode, InterpolationNode,
@ -196,7 +195,7 @@ export function generate(
push(`const { ${ast.imports.join(', ')} } = Vue\n`) push(`const { ${ast.imports.join(', ')} } = Vue\n`)
} else { } else {
// save Vue in a separate variable to avoid collision // save Vue in a separate variable to avoid collision
push(`const _Vue = Vue`) push(`const _Vue = Vue\n`)
} }
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
@ -222,6 +221,7 @@ export function generate(
if (hasImports) { if (hasImports) {
push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`) push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`)
newline() newline()
newline()
} }
} else { } else {
push(`const _ctx = this`) push(`const _ctx = this`)
@ -471,44 +471,7 @@ function genComment(node: CommentNode, context: CodegenContext) {
// control flow // control flow
function genIf(node: IfNode, context: CodegenContext) { function genIf(node: IfNode, context: CodegenContext) {
genIfBranch(node.branches[0], node.branches, 1, context) genNode(node.codegenNode, context)
}
function genIfBranch(
{ condition, children }: IfBranchNode,
branches: IfBranchNode[],
nextIndex: number,
context: CodegenContext
) {
if (condition) {
// v-if or v-else-if
const { push, indent, deindent, newline } = context
if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsQuote = !isSimpleIdentifier(condition.content)
needsQuote && push(`(`)
genExpression(condition, context)
needsQuote && push(`)`)
} else {
genCompoundExpression(condition, context)
}
indent()
context.indentLevel++
push(`? `)
genChildren(children, context, true)
context.indentLevel--
newline()
push(`: `)
if (nextIndex < branches.length) {
genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
} else {
context.push(`null`)
}
deindent(true /* without newline */)
} else {
// v-else
__DEV__ && assert(nextIndex === branches.length)
genChildren(children, context, true)
}
} }
function genFor(node: ForNode, context: CodegenContext) { function genFor(node: ForNode, context: CodegenContext) {
@ -623,7 +586,14 @@ function genConditionalExpression(
context.indentLevel-- context.indentLevel--
newline() newline()
push(`: `) push(`: `)
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
if (!isNested) {
context.indentLevel++
}
genNode(alternate, context) genNode(alternate, context)
if (!isNested) {
context.indentLevel--
}
deindent(true /* without newline */) deindent(true /* without newline */)
} }

View File

@ -62,6 +62,7 @@ export const enum ErrorCodes {
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END, X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
// transform errors // transform errors
X_IF_NO_EXPRESSION,
X_ELSE_IF_NO_ADJACENT_IF, X_ELSE_IF_NO_ADJACENT_IF,
X_ELSE_NO_ADJACENT_IF, X_ELSE_NO_ADJACENT_IF,
X_FOR_NO_EXPRESSION, X_FOR_NO_EXPRESSION,
@ -138,9 +139,10 @@ export const errorMessages: { [code: number]: string } = {
'Note that dynamic directive argument connot contain spaces.', 'Note that dynamic directive argument connot contain spaces.',
// transform errors // transform errors
[ErrorCodes.X_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`, [ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`,
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`, [ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`,
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression.`, [ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`, [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`, [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,

View File

@ -5,6 +5,9 @@ export const PORTAL = `Portal`
export const COMMENT = `Comment` export const COMMENT = `Comment`
export const TEXT = `Text` export const TEXT = `Text`
export const SUSPENSE = `Suspense` export const SUSPENSE = `Suspense`
export const EMPTY = `Empty`
export const OPEN_BLOCK = `openBlock`
export const CREATE_BLOCK = `createBlock`
export const CREATE_VNODE = `createVNode` export const CREATE_VNODE = `createVNode`
export const RESOLVE_COMPONENT = `resolveComponent` export const RESOLVE_COMPONENT = `resolveComponent`
export const RESOLVE_DIRECTIVE = `resolveDirective` export const RESOLVE_DIRECTIVE = `resolveDirective`

View File

@ -148,7 +148,6 @@ export function buildProps(
patchFlag: number patchFlag: number
dynamicPropNames: string[] dynamicPropNames: string[]
} { } {
let isStatic = true
let properties: ObjectExpression['properties'] = [] let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = [] const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = [] const runtimeDirectives: DirectiveNode[] = []
@ -180,13 +179,11 @@ export function buildProps(
value ? value.content : '', value ? value.content : '',
true, true,
value ? value.loc : loc value ? value.loc : loc
), )
loc
) )
) )
} else { } else {
// directives // directives
isStatic = false
const { name, arg, exp, loc } = prop const { name, arg, exp, loc } = prop
// skip v-slot - it is handled by its dedicated transform. // skip v-slot - it is handled by its dedicated transform.
@ -297,11 +294,6 @@ export function buildProps(
) )
} }
// hoist the object if it's fully static
if (isStatic && propsExpression) {
propsExpression = context.hoist(propsExpression)
}
// determine the flags to add // determine the flags to add
if (hasDynammicKeys) { if (hasDynammicKeys) {
patchFlag |= PatchFlags.FULL_PROPS patchFlag |= PatchFlags.FULL_PROPS
@ -391,8 +383,7 @@ function createDirectiveArgs(
dir.modifiers.map(modifier => dir.modifiers.map(modifier =>
createObjectProperty( createObjectProperty(
createSimpleExpression(modifier, true, loc), createSimpleExpression(modifier, true, loc),
createSimpleExpression(`true`, false, loc), createSimpleExpression(`true`, false, loc)
loc
) )
), ),
loc loc

View File

@ -192,7 +192,7 @@ export function processExpression(
let ret let ret
if (children.length) { if (children.length) {
ret = createCompoundExpression(children, node.loc) ret = createCompoundExpression(children)
} else { } else {
ret = node ret = node
} }

View File

@ -43,16 +43,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
arg.content === `name` arg.content === `name`
) { ) {
// dynamic :name="xxx" // dynamic :name="xxx"
slot = createCompoundExpression( slot = createCompoundExpression([
[
$slots + `[`, $slots + `[`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ...(exp.type === NodeTypes.SIMPLE_EXPRESSION
? [exp] ? [exp]
: exp.children), : exp.children),
`]` `]`
], ])
loc
)
nameIndex = i nameIndex = i
break break
} }

View File

@ -30,8 +30,7 @@ export const transformBind: DirectiveTransform = (dir, context) => {
return { return {
props: createObjectProperty( props: createObjectProperty(
arg!, arg!,
exp || createSimpleExpression('', true, loc), exp || createSimpleExpression('', true, loc)
loc
), ),
needRuntime: false needRuntime: false
} }

View File

@ -1,6 +1,7 @@
import { import {
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
traverseChildren traverseChildren,
TransformContext
} from '../transform' } from '../transform'
import { import {
NodeTypes, NodeTypes,
@ -8,25 +9,66 @@ import {
ElementNode, ElementNode,
DirectiveNode, DirectiveNode,
IfBranchNode, IfBranchNode,
SimpleExpressionNode SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
JSChildNode,
ObjectExpression,
createObjectProperty,
Property
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
EMPTY,
FRAGMENT,
APPLY_DIRECTIVES
} from '../runtimeConstants'
import { isString } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform( export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
(node, dir, context) => { (node, dir, context) => {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, loc))
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) { if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied // dir.exp can only be simple expression because vIf transform is applied
// before expression transform. // before expression transform.
processExpression(dir.exp as SimpleExpressionNode, context) dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
} }
if (dir.name === 'if') { if (dir.name === 'if') {
const codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK))
])
context.replaceNode({ context.replaceNode({
type: NodeTypes.IF, type: NodeTypes.IF,
loc: node.loc, loc: node.loc,
branches: [createIfBranch(node, dir)] branches: [createIfBranch(node, dir)],
codegenNode
}) })
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
codegenNode.expressions.push(
createCodegenNodeForBranch(node, dir, 0, context)
)
}
} else { } else {
// locate the adjacent v-if // locate the adjacent v-if
const siblings = context.parent!.children const siblings = context.parent!.children
@ -50,6 +92,25 @@ export const transformIf = createStructuralDirectiveTransform(
// since the branch was removed, it will not be traversed. // since the branch was removed, it will not be traversed.
// make sure to traverse here. // make sure to traverse here.
traverseChildren(branch, context) traverseChildren(branch, context)
// attach this branch's codegen node to the v-if root.
let parentCondition = sibling.codegenNode
.expressions[1] as ConditionalExpression
while (true) {
if (
parentCondition.alternate.type ===
NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
parentCondition = parentCondition.alternate
} else {
parentCondition.alternate = createCodegenNodeForBranch(
node,
dir,
sibling.branches.length - 1,
context
)
break
}
}
} else { } else {
context.onError( context.onError(
createCompilerError( createCompilerError(
@ -74,3 +135,74 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node] children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
} }
} }
function createCodegenNodeForBranch(
node: ElementNode,
dir: DirectiveNode,
index: number,
context: TransformContext
): ConditionalExpression | CallExpression {
if (dir.exp) {
return createConditionalExpression(
dir.exp,
createChildrenCodegenNode(node, index, context),
createCallExpression(context.helper(CREATE_BLOCK), [
context.helper(EMPTY)
])
)
} else {
return createChildrenCodegenNode(node, index, context)
}
}
function createChildrenCodegenNode(
node: ElementNode,
index: number,
{ helper }: TransformContext
): CallExpression {
const isTemplate = node.tagType === ElementTypes.TEMPLATE
const keyExp = `{ key: ${index} }`
if (isTemplate) {
return createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
keyExp,
node.children
])
} else {
let childCodegen = node.codegenNode!
if (childCodegen.callee === helper(APPLY_DIRECTIVES)) {
childCodegen = childCodegen.arguments[0] as CallExpression
}
// change child to a block
childCodegen.callee = helper(CREATE_BLOCK)
// branch key
const existingProps = childCodegen.arguments[1]
if (!existingProps || existingProps === `null`) {
childCodegen.arguments[1] = keyExp
} else {
// inject branch key if not already have a key
const props = existingProps as CallExpression | ObjectExpression
if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// merged props... add ours
// only inject key to object literal if it's the first argument so that
// if doesn't override user provided keys
const first = props.arguments[0] as string | JSChildNode
if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
first.properties.unshift(createKeyProperty(index))
} else {
props.arguments.unshift(keyExp)
}
} else {
props.properties.unshift(createKeyProperty(index))
}
}
return childCodegen
}
}
function createKeyProperty(index: number): Property {
return createObjectProperty(
createSimpleExpression(`key`, true),
createSimpleExpression(index + '', false)
)
}

View File

@ -27,7 +27,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
arg.loc arg.loc
) )
} else { } else {
eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc) eventName = createCompoundExpression([`"on" + (`, arg, `)`])
} }
} else { } else {
// already a compound epxression. // already a compound epxression.
@ -40,8 +40,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
return { return {
props: createObjectProperty( props: createObjectProperty(
eventName, eventName,
exp || createSimpleExpression(`() => {}`, false, loc), exp || createSimpleExpression(`() => {}`, false, loc)
loc
), ),
needRuntime: false needRuntime: false
} }

View File

@ -144,7 +144,6 @@ function buildSlot(
slotProps, slotProps,
children, children,
children.length ? children[0].loc : loc children.length ? children[0].loc : loc
), )
loc
) )
} }