diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index e3888687..eb1c55a7 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -105,9 +105,11 @@ return function render() { exports[`compiler: codegen function mode preamble 1`] = ` "const _Vue = Vue + return function render() { with (this) { const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue + return null } }" @@ -137,24 +139,7 @@ exports[`compiler: codegen ifNode 1`] = ` " return function render() { with (this) { - return foo - ? \\"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 + return (foo, bar) } }" `; diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index 047d00ad..2bd76651 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -2,17 +2,27 @@ exports[`compiler: integration tests function mode 1`] = ` "const _Vue = Vue + return function render() { 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\\", { id: \\"foo\\", class: bar.baz }, [ _toString(world.burn()), - ok - ? _createVNode(\\"div\\", null, \\"yes\\") - : \\"no\\", + (_openBlock(), ok + ? _createBlock( + \\"div\\", + { key: 0 }, + \\"yes\\" + ) + : _createBlock( + _Fragment, + { key: 1 }, + \\"no\\" + )), _renderList(list, (value, index) => { return _createVNode(\\"div\\", null, [ _createVNode(\\"span\\", null, _toString(value + index)) @@ -24,7 +34,7 @@ return function render() { `; 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() { const _ctx = this @@ -33,9 +43,17 @@ return function render() { class: _ctx.bar.baz }, [ toString(_ctx.world.burn()), - (_ctx.ok) - ? createVNode(\\"div\\", null, \\"yes\\") - : \\"no\\", + (openBlock(), (_ctx.ok) + ? createBlock( + \\"div\\", + { key: 0 }, + \\"yes\\" + ) + : createBlock( + Fragment, + { key: 1 }, + \\"no\\" + )), renderList(_ctx.list, (value, index) => { return createVNode(\\"div\\", null, [ createVNode(\\"span\\", null, toString(value + index)) @@ -46,7 +64,7 @@ return function render() { `; 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() { const _ctx = this @@ -55,9 +73,17 @@ export default function render() { class: _ctx.bar.baz }, [ _toString(_ctx.world.burn()), - (_ctx.ok) - ? createVNode(\\"div\\", null, \\"yes\\") - : \\"no\\", + (openBlock(), (_ctx.ok) + ? createBlock( + \\"div\\", + { key: 0 }, + \\"yes\\" + ) + : createBlock( + Fragment, + { key: 1 }, + \\"no\\" + )), _renderList(_ctx.list, (value, index) => { return createVNode(\\"div\\", null, [ createVNode(\\"span\\", null, _toString(value + index)) diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 93de38fb..3e2b48d1 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -12,7 +12,8 @@ import { createArrayExpression, ElementNode, createCompoundExpression, - createInterpolation + createInterpolation, + createSequenceExpression } from '../src' import { CREATE_VNODE, @@ -100,8 +101,7 @@ describe('compiler: codegen', () => { [ createObjectProperty( createSimpleExpression(`id`, true, mockLoc), - createSimpleExpression(`foo`, true, mockLoc), - mockLoc + createSimpleExpression(`foo`, true, mockLoc) ) ], mockLoc @@ -226,19 +226,16 @@ describe('compiler: codegen', () => { const { code } = generate( createRoot({ children: [ - createCompoundExpression( - [ - `_ctx.`, - createSimpleExpression(`foo`, false, mockLoc), - ` + `, - { - type: NodeTypes.INTERPOLATION, - loc: mockLoc, - content: createSimpleExpression(`bar`, false, mockLoc) - } - ], - mockLoc - ) + createCompoundExpression([ + `_ctx.`, + createSimpleExpression(`foo`, false, mockLoc), + ` + `, + { + type: NodeTypes.INTERPOLATION, + loc: mockLoc, + content: createSimpleExpression(`bar`, false, mockLoc) + } + ]) ] }) ) @@ -253,90 +250,16 @@ describe('compiler: codegen', () => { { 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)] - }, - { - type: NodeTypes.IF_BRANCH, - condition: undefined, - loc: mockLoc, - children: [ - { - type: NodeTypes.COMMENT, - content: 'foo', - loc: mockLoc - } - ] - } - ] + branches: [], + codegenNode: createSequenceExpression([ + createSimpleExpression('foo', false), + createSimpleExpression('bar', false) + ]) } ] }) ) - expect(code).toMatch(` - 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).toMatch(`return (foo, bar)`) expect(code).toMatchSnapshot() }) @@ -570,13 +493,11 @@ describe('compiler: codegen', () => { [ createObjectProperty( createSimpleExpression(`id`, true, mockLoc), - createSimpleExpression(`foo`, true, mockLoc), - mockLoc + createSimpleExpression(`foo`, true, mockLoc) ), createObjectProperty( createSimpleExpression(`prop`, false, mockLoc), - createSimpleExpression(`bar`, false, mockLoc), - mockLoc + createSimpleExpression(`bar`, false, mockLoc) ), // compound expression as computed key createObjectProperty( @@ -588,8 +509,7 @@ describe('compiler: codegen', () => { createSimpleExpression(`bar`, false, mockLoc) ] }, - createSimpleExpression(`bar`, false, mockLoc), - mockLoc + createSimpleExpression(`bar`, false, mockLoc) ) ], mockLoc @@ -603,8 +523,7 @@ describe('compiler: codegen', () => { createObjectProperty( // should quote the key! createSimpleExpression(`some-key`, true, mockLoc), - createSimpleExpression(`foo`, true, mockLoc), - mockLoc + createSimpleExpression(`foo`, true, mockLoc) ) ], mockLoc @@ -641,4 +560,8 @@ describe('compiler: codegen', () => { ])`) expect(code).toMatchSnapshot() }) + + test.todo('SequenceExpression') + + test.todo('ConditionalExpression') }) diff --git a/packages/compiler-core/__tests__/compile.spec.ts b/packages/compiler-core/__tests__/compile.spec.ts index 951702ba..66851951 100644 --- a/packages/compiler-core/__tests__/compile.spec.ts +++ b/packages/compiler-core/__tests__/compile.spec.ts @@ -50,10 +50,6 @@ describe('compiler: integration tests', () => { filename: `foo.vue` }) - expect(code).toMatch( - `const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue` - ) - expect(code).toMatchSnapshot() expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sourcesContent).toEqual([source]) @@ -120,8 +116,6 @@ describe('compiler: integration tests', () => { prefixIdentifiers: true }) - expect(code).toMatch(`const { createVNode, toString, renderList } = Vue`) - expect(code).toMatchSnapshot() expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sourcesContent).toEqual([source]) @@ -197,10 +191,6 @@ describe('compiler: integration tests', () => { filename: `foo.vue` }) - expect(code).toMatch( - `import { createVNode, toString, renderList } from "vue"` - ) - expect(code).toMatchSnapshot() expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sourcesContent).toEqual([source]) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap index 921cbec1..89da629e 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap @@ -2,9 +2,11 @@ exports[`compiler: optimize interpolation consecutive text 1`] = ` "const _Vue = Vue + return function render() { with (this) { const { toString: _toString } = _Vue + return _toString(foo) + \\" bar \\" + _toString(baz) } }" @@ -12,9 +14,11 @@ return function render() { exports[`compiler: optimize interpolation consecutive text between elements 1`] = ` "const _Vue = Vue + return function render() { with (this) { const { createVNode: _createVNode, toString: _toString } = _Vue + return [ _createVNode(\\"div\\"), _toString(foo) + \\" bar \\" + _toString(baz), @@ -26,9 +30,11 @@ return function render() { exports[`compiler: optimize interpolation consecutive text mixed with elements 1`] = ` "const _Vue = Vue + return function render() { with (this) { const { createVNode: _createVNode, toString: _toString } = _Vue + return [ _createVNode(\\"div\\"), _toString(foo) + \\" bar \\" + _toString(baz), @@ -42,9 +48,11 @@ return function render() { exports[`compiler: optimize interpolation no consecutive text 1`] = ` "const _Vue = Vue + return function render() { with (this) { const { toString: _toString } = _Vue + return _toString(foo) } }" diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 2af63cb8..8f2cce9e 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -74,42 +74,25 @@ describe('compiler: element transform', () => { }) test('static props', () => { - const { root, node } = parseWithElementTransform( - `
` - ) + const { node } = parseWithElementTransform(`
`) expect(node.callee).toBe(`_${CREATE_VNODE}`) - // should hoist the static object - expect(root.hoists).toMatchObject([ + expect(node.arguments).toMatchObject([ + `"div"`, createStaticObjectMatcher({ id: 'foo', class: 'bar' }) ]) - expect(node.arguments).toMatchObject([ - `"div"`, - { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_hoisted_1` - } - ]) }) test('props + children', () => { - const { root, node } = parseWithElementTransform( - `
` - ) + const { node } = parseWithElementTransform(`
`) expect(node.callee).toBe(`_${CREATE_VNODE}`) - expect(root.hoists).toMatchObject([ - createStaticObjectMatcher({ - id: 'foo' - }) - ]) expect(node.arguments).toMatchObject([ `"div"`, - { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_hoisted_1` - }, + createStaticObjectMatcher({ + id: 'foo' + }), [ { type: NodeTypes.ELEMENT, @@ -298,7 +281,7 @@ describe('compiler: element transform', () => { foo(dir) { _dir = dir return { - props: createObjectProperty(dir.arg!, dir.exp!, dir.loc), + props: createObjectProperty(dir.arg!, dir.exp!), needRuntime: false } } @@ -326,7 +309,7 @@ describe('compiler: element transform', () => { foo(dir) { _dir = dir return { - props: [createObjectProperty(dir.arg!, dir.exp!, dir.loc)], + props: [createObjectProperty(dir.arg!, dir.exp!)], needRuntime: true } } diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index cf806f8a..01047429 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -5,7 +5,6 @@ import { DirectiveNode, NodeTypes, CompilerOptions, - IfNode, InterpolationNode } from '../../src' import { transformIf } from '../../src/transforms/vIf' @@ -159,14 +158,6 @@ describe('compiler: expression transform', () => { }) }) - test('should prefix v-if condition', () => { - const node = parseWithExpressionTransform(`
`) as IfNode - expect(node.branches[0].condition).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_ctx.ok` - }) - }) - test('should not prefix whitelisted globals', () => { const node = parseWithExpressionTransform( `{{ Math.max(1, 2) }}` diff --git a/packages/compiler-core/__tests__/transforms/vBind.spec.ts b/packages/compiler-core/__tests__/transforms/vBind.spec.ts index 5c3f252c..0f6a1a1a 100644 --- a/packages/compiler-core/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vBind.spec.ts @@ -61,16 +61,6 @@ describe('compiler: transform v-bind', () => { column: 19 } } - }, - loc: { - start: { - line: 1, - column: 6 - }, - end: { - line: 1, - column: 20 - } } }) }) diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index dcac2812..5871476f 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -1,6 +1,7 @@ import { parse } from '../../src/parse' import { transform } from '../../src/transform' import { transformIf } from '../../src/transforms/vIf' +import { transformElement } from '../../src/transforms/transformElement' import { IfNode, NodeTypes, @@ -18,7 +19,10 @@ function parseWithIfTransform( returnIndex: number = 0 ): IfNode { const node = parse(template, options) - transform(node, { nodeTransforms: [transformIf], ...options }) + transform(node, { + nodeTransforms: [transformIf, transformElement], + ...options + }) if (!options.onError) { expect(node.children.length).toBe(1) expect(node.children[0].type).toBe(NodeTypes.IF) @@ -153,67 +157,87 @@ describe('compiler: transform v-if', () => { expect((b3.children[1] as TextNode).content).toBe(`fine`) }) - test('error on v-else missing adjacent v-if', () => { - const onError = jest.fn() - - const node1 = parseWithIfTransform(`
`, { onError }) - expect(onError.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: node1.loc - } - ]) - - const node2 = parseWithIfTransform(`
`, { onError }, 1) - expect(onError.mock.calls[1]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: node2.loc - } - ]) - - const node3 = parseWithIfTransform(`
foo
`, { onError }, 2) - expect(onError.mock.calls[2]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: node3.loc - } - ]) + test('should prefix v-if condition', () => { + const node = parseWithIfTransform(`
`, { + prefixIdentifiers: true + }) as IfNode + expect(node.branches[0].condition).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.ok` + }) }) - test('error on v-else-if missing adjacent v-if', () => { - const onError = jest.fn() + describe('codegen', () => { + // TODO + }) - const node1 = parseWithIfTransform(`
`, { onError }) - expect(onError.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: node1.loc - } - ]) + describe('errors', () => { + test('error on v-else missing adjacent v-if', () => { + const onError = jest.fn() - const node2 = parseWithIfTransform( - `
`, - { onError }, - 1 - ) - expect(onError.mock.calls[1]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: node2.loc - } - ]) + const node1 = parseWithIfTransform(`
`, { onError }) + expect(onError.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: node1.loc + } + ]) - const node3 = parseWithIfTransform( - `
foo
`, - { onError }, - 2 - ) - expect(onError.mock.calls[2]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: node3.loc - } - ]) + const node2 = parseWithIfTransform(`
`, { onError }, 1) + expect(onError.mock.calls[1]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: node2.loc + } + ]) + + const node3 = parseWithIfTransform( + `
foo
`, + { onError }, + 2 + ) + expect(onError.mock.calls[2]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: node3.loc + } + ]) + }) + + test('error on v-else-if missing adjacent v-if', () => { + const onError = jest.fn() + + const node1 = parseWithIfTransform(`
`, { onError }) + expect(onError.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: node1.loc + } + ]) + + const node2 = parseWithIfTransform( + `
`, + { onError }, + 1 + ) + expect(onError.mock.calls[1]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: node2.loc + } + ]) + + const node3 = parseWithIfTransform( + `
foo
`, + { onError }, + 2 + ) + expect(onError.mock.calls[2]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: node3.loc + } + ]) + }) }) }) diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts index c0cffa66..8e92123d 100644 --- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts @@ -58,16 +58,6 @@ describe('compiler: transform v-on', () => { column: 25 } } - }, - loc: { - start: { - line: 1, - column: 6 - }, - end: { - line: 1, - column: 26 - } } }) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index b894be85..eafe9e8f 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -142,7 +142,7 @@ export interface CompoundExpressionNode extends Node { export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] - codegenNode: JSChildNode | undefined + codegenNode: SequenceExpression } export interface IfBranchNode extends Node { @@ -212,9 +212,20 @@ export interface ConditionalExpression extends Node { 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( elements: ArrayExpression['elements'], - loc: SourceLocation + loc: SourceLocation = locStub ): ArrayExpression { return { type: NodeTypes.JS_ARRAY_EXPRESSION, @@ -225,7 +236,7 @@ export function createArrayExpression( export function createObjectExpression( properties: Property[], - loc: SourceLocation + loc: SourceLocation = locStub ): ObjectExpression { return { type: NodeTypes.JS_OBJECT_EXPRESSION, @@ -236,12 +247,11 @@ export function createObjectExpression( export function createObjectProperty( key: ExpressionNode, - value: JSChildNode, - loc: SourceLocation + value: JSChildNode ): Property { return { type: NodeTypes.JS_PROPERTY, - loc, + loc: locStub, key, value } @@ -250,7 +260,7 @@ export function createObjectProperty( export function createSimpleExpression( content: string, isStatic: boolean, - loc: SourceLocation + loc: SourceLocation = locStub ): SimpleExpressionNode { return { type: NodeTypes.SIMPLE_EXPRESSION, @@ -274,20 +284,19 @@ export function createInterpolation( } export function createCompoundExpression( - children: CompoundExpressionNode['children'], - loc: SourceLocation + children: CompoundExpressionNode['children'] ): CompoundExpressionNode { return { type: NodeTypes.COMPOUND_EXPRESSION, - loc, + loc: locStub, children } } export function createCallExpression( callee: string, - args: CallExpression['arguments'], - loc: SourceLocation + args: CallExpression['arguments'] = [], + loc: SourceLocation = locStub ): CallExpression { return { type: NodeTypes.JS_CALL_EXPRESSION, @@ -300,7 +309,7 @@ export function createCallExpression( export function createFunctionExpression( params: ExpressionNode | undefined, returns: TemplateChildNode[], - loc: SourceLocation + loc: SourceLocation = locStub ): SlotFunctionExpression { return { type: NodeTypes.JS_SLOT_FUNCTION, @@ -310,15 +319,9 @@ export function createFunctionExpression( } } -// sequence and conditional expressions are never associated with template nodes, -// so their source locations are just a stub. -const locStub: SourceLocation = { - source: '', - start: { line: 1, column: 1, offset: 0 }, - end: { line: 1, column: 1, offset: 0 } -} - -export function createSequenceExpression(expressions: JSChildNode[]) { +export function createSequenceExpression( + expressions: JSChildNode[] +): SequenceExpression { return { type: NodeTypes.JS_SEQUENCE_EXPRESSION, expressions, @@ -330,7 +333,7 @@ export function createConditionalExpression( test: ExpressionNode, consequent: JSChildNode, alternate: JSChildNode -) { +): ConditionalExpression { return { type: NodeTypes.JS_CONDITIONAL_EXPRESSION, test, diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 0d01772a..5f80c074 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -12,7 +12,6 @@ import { CallExpression, ArrayExpression, ObjectExpression, - IfBranchNode, SourceLocation, Position, InterpolationNode, @@ -196,7 +195,7 @@ export function generate( push(`const { ${ast.imports.join(', ')} } = Vue\n`) } else { // save Vue in a separate variable to avoid collision - push(`const _Vue = Vue`) + push(`const _Vue = Vue\n`) } } genHoists(ast.hoists, context) @@ -222,6 +221,7 @@ export function generate( if (hasImports) { push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`) newline() + newline() } } else { push(`const _ctx = this`) @@ -471,44 +471,7 @@ function genComment(node: CommentNode, context: CodegenContext) { // control flow function genIf(node: IfNode, context: CodegenContext) { - genIfBranch(node.branches[0], node.branches, 1, 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) - } + genNode(node.codegenNode, context) } function genFor(node: ForNode, context: CodegenContext) { @@ -623,7 +586,14 @@ function genConditionalExpression( context.indentLevel-- newline() push(`: `) + const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION + if (!isNested) { + context.indentLevel++ + } genNode(alternate, context) + if (!isNested) { + context.indentLevel-- + } deindent(true /* without newline */) } diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 559a44fc..4ec54b74 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -62,6 +62,7 @@ export const enum ErrorCodes { X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END, // transform errors + X_IF_NO_EXPRESSION, X_ELSE_IF_NO_ADJACENT_IF, X_ELSE_NO_ADJACENT_IF, X_FOR_NO_EXPRESSION, @@ -138,9 +139,10 @@ export const errorMessages: { [code: number]: string } = { 'Note that dynamic directive argument connot contain spaces.', // 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_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_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`, diff --git a/packages/compiler-core/src/runtimeConstants.ts b/packages/compiler-core/src/runtimeConstants.ts index 79e3d09b..6276f21b 100644 --- a/packages/compiler-core/src/runtimeConstants.ts +++ b/packages/compiler-core/src/runtimeConstants.ts @@ -5,6 +5,9 @@ export const PORTAL = `Portal` export const COMMENT = `Comment` export const TEXT = `Text` 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 RESOLVE_COMPONENT = `resolveComponent` export const RESOLVE_DIRECTIVE = `resolveDirective` diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 469f4e09..a697cc9d 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -148,7 +148,6 @@ export function buildProps( patchFlag: number dynamicPropNames: string[] } { - let isStatic = true let properties: ObjectExpression['properties'] = [] const mergeArgs: PropsExpression[] = [] const runtimeDirectives: DirectiveNode[] = [] @@ -180,13 +179,11 @@ export function buildProps( value ? value.content : '', true, value ? value.loc : loc - ), - loc + ) ) ) } else { // directives - isStatic = false const { name, arg, exp, loc } = prop // 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 if (hasDynammicKeys) { patchFlag |= PatchFlags.FULL_PROPS @@ -391,8 +383,7 @@ function createDirectiveArgs( dir.modifiers.map(modifier => createObjectProperty( createSimpleExpression(modifier, true, loc), - createSimpleExpression(`true`, false, loc), - loc + createSimpleExpression(`true`, false, loc) ) ), loc diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 439fa19b..6cbb5962 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -192,7 +192,7 @@ export function processExpression( let ret if (children.length) { - ret = createCompoundExpression(children, node.loc) + ret = createCompoundExpression(children) } else { ret = node } diff --git a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts index 1c4e07dd..24032550 100644 --- a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts +++ b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts @@ -43,16 +43,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { arg.content === `name` ) { // dynamic :name="xxx" - slot = createCompoundExpression( - [ - $slots + `[`, - ...(exp.type === NodeTypes.SIMPLE_EXPRESSION - ? [exp] - : exp.children), - `]` - ], - loc - ) + slot = createCompoundExpression([ + $slots + `[`, + ...(exp.type === NodeTypes.SIMPLE_EXPRESSION + ? [exp] + : exp.children), + `]` + ]) nameIndex = i break } diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index b8a71c4b..fc774e45 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -30,8 +30,7 @@ export const transformBind: DirectiveTransform = (dir, context) => { return { props: createObjectProperty( arg!, - exp || createSimpleExpression('', true, loc), - loc + exp || createSimpleExpression('', true, loc) ), needRuntime: false } diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 966cc41b..9c415fd6 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -1,6 +1,7 @@ import { createStructuralDirectiveTransform, - traverseChildren + traverseChildren, + TransformContext } from '../transform' import { NodeTypes, @@ -8,25 +9,66 @@ import { ElementNode, DirectiveNode, IfBranchNode, - SimpleExpressionNode + SimpleExpressionNode, + createSequenceExpression, + createCallExpression, + createConditionalExpression, + ConditionalExpression, + CallExpression, + createSimpleExpression, + JSChildNode, + ObjectExpression, + createObjectProperty, + Property } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' +import { + OPEN_BLOCK, + CREATE_BLOCK, + EMPTY, + FRAGMENT, + APPLY_DIRECTIVES +} from '../runtimeConstants' +import { isString } from '@vue/shared' export const transformIf = createStructuralDirectiveTransform( /^(if|else|else-if)$/, (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) { // dir.exp can only be simple expression because vIf transform is applied // before expression transform. - processExpression(dir.exp as SimpleExpressionNode, context) + dir.exp = processExpression(dir.exp as SimpleExpressionNode, context) } + if (dir.name === 'if') { + const codegenNode = createSequenceExpression([ + createCallExpression(context.helper(OPEN_BLOCK)) + ]) + context.replaceNode({ type: NodeTypes.IF, 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 { // locate the adjacent v-if const siblings = context.parent!.children @@ -50,6 +92,25 @@ export const transformIf = createStructuralDirectiveTransform( // since the branch was removed, it will not be traversed. // make sure to traverse here. 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 { context.onError( createCompilerError( @@ -74,3 +135,74 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode { 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) + ) +} diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 9cc2f63c..2c89038f 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -27,7 +27,7 @@ export const transformOn: DirectiveTransform = (dir, context) => { arg.loc ) } else { - eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc) + eventName = createCompoundExpression([`"on" + (`, arg, `)`]) } } else { // already a compound epxression. @@ -40,8 +40,7 @@ export const transformOn: DirectiveTransform = (dir, context) => { return { props: createObjectProperty( eventName, - exp || createSimpleExpression(`() => {}`, false, loc), - loc + exp || createSimpleExpression(`() => {}`, false, loc) ), needRuntime: false } diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 92ab4a6b..7a1a0f7d 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -144,7 +144,6 @@ function buildSlot( slotProps, children, children.length ? children[0].loc : loc - ), - loc + ) ) }