From b43f3b61b7e7d367af9823545befb2a75a0e856a Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 25 Sep 2019 14:13:33 -0400 Subject: [PATCH] feat(compiler): transformStyle + context.hoist --- .../__snapshots__/codegen.spec.ts.snap | 55 ++++++++--- .../__snapshots__/parse.spec.ts.snap | 97 +++++++++++++++++++ .../compiler-core/__tests__/codegen.spec.ts | 25 ++++- .../compiler-core/__tests__/transform.spec.ts | 23 ++++- .../transforms/transformElement.spec.ts | 34 +++++-- packages/compiler-core/src/ast.ts | 1 + packages/compiler-core/src/codegen.ts | 15 ++- packages/compiler-core/src/index.ts | 9 +- packages/compiler-core/src/parse.ts | 1 + packages/compiler-core/src/transform.ts | 16 ++- .../src/transforms/transformElement.ts | 33 ++----- .../src/transforms/transformExpression.ts | 3 +- .../src/transforms/transformStyle.ts | 37 +++++++ 13 files changed, 290 insertions(+), 59 deletions(-) create mode 100644 packages/compiler-core/src/transforms/transformStyle.ts diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 765602697..8ce723cf9 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`compiler: codegen callExpression + objectExpression + arrayExpression 1`] = ` -"return function render() { +" +return function render() { with (this) { return createVNode(\\"div\\", { id: \\"foo\\", @@ -16,7 +17,8 @@ exports[`compiler: codegen callExpression + objectExpression + arrayExpression 1 `; exports[`compiler: codegen comment 1`] = ` -"return function render() { +" +return function render() { with (this) { return createVNode(Comment, 0, \\"foo\\") } @@ -24,7 +26,8 @@ exports[`compiler: codegen comment 1`] = ` `; exports[`compiler: codegen compound expression 1`] = ` -"return function render() { +" +return function render() { with (this) { return toString(_ctx.foo) } @@ -32,7 +35,8 @@ exports[`compiler: codegen compound expression 1`] = ` `; exports[`compiler: codegen forNode 1`] = ` -"return function render() { +" +return function render() { with (this) { return renderList(list, (v, k, i) => toString(v)) } @@ -40,7 +44,8 @@ exports[`compiler: codegen forNode 1`] = ` `; exports[`compiler: codegen forNode w/ skipped key alias 1`] = ` -"return function render() { +" +return function render() { with (this) { return renderList(list, (v, __key, i) => toString(v)) } @@ -48,7 +53,8 @@ exports[`compiler: codegen forNode w/ skipped key alias 1`] = ` `; exports[`compiler: codegen forNode w/ skipped value alias 1`] = ` -"return function render() { +" +return function render() { with (this) { return renderList(list, (__value, k, i) => toString(v)) } @@ -56,7 +62,8 @@ exports[`compiler: codegen forNode w/ skipped value alias 1`] = ` `; exports[`compiler: codegen forNode w/ skipped value and key aliases 1`] = ` -"return function render() { +" +return function render() { with (this) { return renderList(list, (__value, __key, i) => toString(v)) } @@ -73,8 +80,20 @@ return function render() { }" `; +exports[`compiler: codegen hoists 1`] = ` +"const _hoisted_1 = hello +const _hoisted_2 = { id: \\"foo\\" } + +return function render() { + with (this) { + return null + } +}" +`; + exports[`compiler: codegen ifNode 1`] = ` -"return function render() { +" +return function render() { with (this) { return foo ? \\"foo\\" @@ -86,7 +105,8 @@ exports[`compiler: codegen ifNode 1`] = ` `; exports[`compiler: codegen ifNode with no v-else 1`] = ` -"return function render() { +" +return function render() { with (this) { return foo ? \\"foo\\" @@ -98,7 +118,8 @@ exports[`compiler: codegen ifNode with no v-else 1`] = ` `; exports[`compiler: codegen interpolation 1`] = ` -"return function render() { +" +return function render() { with (this) { return toString(hello) } @@ -116,14 +137,16 @@ export default function render() { `; exports[`compiler: codegen prefixIdentifiers: true should inject _ctx statement 1`] = ` -"return function render() { +" +return function render() { const _ctx = this return null }" `; -exports[`compiler: codegen statement preambles 1`] = ` -"return function render() { +exports[`compiler: codegen statements 1`] = ` +" +return function render() { const a = 1 const b = 2 @@ -134,7 +157,8 @@ exports[`compiler: codegen statement preambles 1`] = ` `; exports[`compiler: codegen static text 1`] = ` -"return function render() { +" +return function render() { with (this) { return \\"hello\\" } @@ -142,7 +166,8 @@ exports[`compiler: codegen static text 1`] = ` `; exports[`compiler: codegen text + comment + interpolation 1`] = ` -"return function render() { +" +return function render() { with (this) { return [ \\"foo\\", diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 695935fa2..250b91ae0 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -45,6 +45,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -109,6 +110,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -173,6 +175,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -255,6 +258,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -337,6 +341,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -419,6 +424,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -501,6 +507,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -566,6 +573,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -631,6 +639,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -696,6 +705,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -761,6 +771,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -825,6 +836,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -914,6 +926,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -979,6 +992,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1044,6 +1058,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1109,6 +1124,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1250,6 +1266,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1320,6 +1337,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1390,6 +1408,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1455,6 +1474,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1520,6 +1540,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1590,6 +1611,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1679,6 +1701,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1743,6 +1766,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1807,6 +1831,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1871,6 +1896,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1935,6 +1961,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -1999,6 +2026,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2064,6 +2092,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2129,6 +2158,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2199,6 +2229,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2269,6 +2300,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2358,6 +2390,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2447,6 +2480,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2536,6 +2570,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2642,6 +2677,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2748,6 +2784,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2854,6 +2891,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -2960,6 +2998,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3066,6 +3105,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3172,6 +3212,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3278,6 +3319,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3384,6 +3426,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3448,6 +3491,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3488,6 +3532,7 @@ Object { "type": 3, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3552,6 +3597,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3616,6 +3662,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3680,6 +3727,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3744,6 +3792,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3809,6 +3858,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3875,6 +3925,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -3940,6 +3991,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4022,6 +4074,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4128,6 +4181,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4217,6 +4271,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4306,6 +4361,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4352,6 +4408,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4417,6 +4474,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4482,6 +4540,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4547,6 +4606,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4690,6 +4750,7 @@ class=\\"bar\\">", "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4832,6 +4893,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4896,6 +4958,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -4960,6 +5023,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5024,6 +5088,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5088,6 +5153,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5153,6 +5219,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5218,6 +5285,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5283,6 +5351,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5348,6 +5417,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5454,6 +5524,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5560,6 +5631,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5666,6 +5738,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5772,6 +5845,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5878,6 +5952,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -5984,6 +6059,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6090,6 +6166,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6196,6 +6273,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6285,6 +6363,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6391,6 +6470,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6455,6 +6535,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6562,6 +6643,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6627,6 +6709,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6692,6 +6775,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6756,6 +6840,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6802,6 +6887,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6848,6 +6934,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6914,6 +7001,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -6979,6 +7067,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7049,6 +7138,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7119,6 +7209,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7160,6 +7251,7 @@ Object { "type": 2, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7201,6 +7293,7 @@ Object { "type": 2, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7243,6 +7336,7 @@ Object { "type": 4, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7316,6 +7410,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7502,6 +7597,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { @@ -7707,6 +7803,7 @@ Object { "type": 1, }, ], + "hoists": Array [], "imports": Array [], "loc": Object { "end": Object { diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 42c73c2b9..5f21af633 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -34,6 +34,7 @@ function createRoot(options: Partial = {}): RootNode { children: [], imports: [], statements: [], + hoists: [], loc: mockLoc, ...options } @@ -58,7 +59,7 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) - test('statement preambles', () => { + test('statements', () => { const root = createRoot({ statements: [`const a = 1`, `const b = 2`] }) @@ -68,6 +69,28 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) + test('hoists', () => { + const root = createRoot({ + hoists: [ + createExpression(`hello`, false, mockLoc), + createObjectExpression( + [ + createObjectProperty( + createExpression(`id`, true, mockLoc), + createExpression(`foo`, true, mockLoc), + mockLoc + ) + ], + mockLoc + ) + ] + }) + const { code } = generate(root) + expect(code).toMatch(`const _hoisted_1 = hello`) + expect(code).toMatch(`const _hoisted_2 = { id: "foo" }`) + expect(code).toMatchSnapshot() + }) + test('prefixIdentifiers: true should inject _ctx statement', () => { const { code } = generate(createRoot(), { prefixIdentifiers: true }) expect(code).toMatch(`const _ctx = this\n`) diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index d30128fd9..8ebf221f9 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -1,6 +1,11 @@ import { parse } from '../src/parse' import { transform, NodeTransform } from '../src/transform' -import { ElementNode, NodeTypes } from '../src/ast' +import { + ElementNode, + NodeTypes, + DirectiveNode, + ExpressionNode +} from '../src/ast' import { ErrorCodes, createCompilerError } from '../src/errors' import { TO_STRING, CREATE_VNODE, COMMENT } from '../src/runtimeConstants' @@ -158,6 +163,22 @@ describe('compiler: transform', () => { expect(spy.mock.calls[1][0]).toBe(d1) }) + test('context.hoist', () => { + const ast = parse(`
`) + const hoisted: ExpressionNode[] = [] + const mock: NodeTransform = (node, context) => { + const dir = (node as ElementNode).props[0] as DirectiveNode + hoisted.push(dir.exp!) + dir.exp = context.hoist(dir.exp!) + } + transform(ast, { + nodeTransforms: [mock] + }) + expect(ast.hoists).toMatchObject(hoisted) + expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`) + expect((ast as any).children[1].props[0].exp.content).toBe(`_hoisted_2`) + }) + test('onError option', () => { const ast = parse(`
`) const loc = ast.children[0].loc diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 137d373b8..1723d0f62 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -71,25 +71,42 @@ describe('compiler: element transform', () => { }) test('static props', () => { - const { node } = parseWithElementTransform(`
`) + const { root, node } = parseWithElementTransform( + `
` + ) expect(node.callee).toBe(CREATE_VNODE) - expect(node.arguments).toMatchObject([ - `"div"`, + // should hoist the static object + expect(root.hoists).toMatchObject([ createStaticObjectMatcher({ id: 'foo', class: 'bar' }) ]) + expect(node.arguments).toMatchObject([ + `"div"`, + { + type: NodeTypes.EXPRESSION, + content: `_hoisted_1` + } + ]) }) test('props + children', () => { - const { node } = parseWithElementTransform(`
`) + const { root, node } = parseWithElementTransform( + `
` + ) expect(node.callee).toBe(CREATE_VNODE) - expect(node.arguments).toMatchObject([ - `"div"`, + expect(root.hoists).toMatchObject([ createStaticObjectMatcher({ id: 'foo' - }), + }) + ]) + expect(node.arguments).toMatchObject([ + `"div"`, + { + type: NodeTypes.EXPRESSION, + content: `_hoisted_1` + }, [ { type: NodeTypes.ELEMENT, @@ -452,7 +469,8 @@ describe('compiler: element transform', () => { test('props dedupe', () => { const { code } = compile( - `
` + `
+
` ) console.log(code) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index de828ef6d..786cdfd4d 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -68,6 +68,7 @@ export interface RootNode extends Node { children: ChildNode[] imports: string[] statements: string[] + hoists: JSChildNode[] } export interface ElementNode extends Node { diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 94d7241f6..b6c66a7d3 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -148,14 +148,16 @@ export function generate( if (mode === 'function') { // generate const declarations for helpers if (imports) { - push(`const { ${imports} } = Vue\n\n`) + push(`const { ${imports} } = Vue\n`) } + genHoists(ast.hoists, context) push(`return `) } else { // generate import statements for helpers if (imports) { - push(`import { ${imports} } from 'vue'\n\n`) + push(`import { ${imports} } from 'vue'\n`) } + genHoists(ast.hoists, context) push(`export default `) } push(`function render() {`) @@ -190,6 +192,15 @@ export function generate( } } +function genHoists(hoists: JSChildNode[], context: CodegenContext) { + hoists.forEach((exp, i) => { + context.push(`const _hoisted_${i + 1} = `) + genNode(exp, context) + context.newline() + }) + context.newline() +} + // This will generate a single vnode call if: // - The list has length === 1, AND: // - This is a root node, OR: diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 70de04696..572abeadf 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -10,6 +10,7 @@ import { transformOn } from './transforms/vOn' import { transformBind } from './transforms/vBind' import { transformExpression } from './transforms/transformExpression' import { defaultOnError, createCompilerError, ErrorCodes } from './errors' +import { transformStyle } from './transforms/transformStyle' export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions @@ -33,6 +34,7 @@ export function compile( transformIf, transformFor, ...(prefixIdentifiers ? [transformExpression] : []), + transformStyle, transformElement, ...(options.nodeTransforms || []) // user transforms ], @@ -53,7 +55,7 @@ export { createStructuralDirectiveTransform, TransformOptions, TransformContext, - NodeTransform as Transform, + NodeTransform, StructuralDirectiveTransform } from './transform' export { @@ -64,8 +66,3 @@ export { } from './codegen' export { ErrorCodes, CompilerError, createCompilerError } from './errors' export * from './ast' - -// debug -export { - transformElement as prepareElementForCodegen -} from './transforms/transformElement' diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 84677fd87..e80480fba 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -84,6 +84,7 @@ export function parse(content: string, options: ParserOptions = {}): RootNode { children: parseChildren(context, TextModes.DATA, []), imports: [], statements: [], + hoists: [], loc: getSelection(context, start) } } diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 4dac7986a..ad62b402b 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -6,7 +6,9 @@ import { ElementNode, DirectiveNode, Property, - ExpressionNode + ExpressionNode, + createExpression, + JSChildNode } from './ast' import { isString, isArray } from '@vue/shared' import { CompilerError, defaultOnError } from './errors' @@ -52,6 +54,7 @@ export interface TransformContext extends Required { root: RootNode imports: Set statements: string[] + hoists: JSChildNode[] identifiers: { [name: string]: number | undefined } parent: ParentNode childIndex: number @@ -61,6 +64,7 @@ export interface TransformContext extends Required { onNodeRemoved: () => void addIdentifier(exp: ExpressionNode): void removeIdentifier(exp: ExpressionNode): void + hoist(exp: JSChildNode): ExpressionNode } function createTransformContext( @@ -76,6 +80,7 @@ function createTransformContext( root, imports: new Set(), statements: [], + hoists: [], identifiers: {}, prefixIdentifiers, nodeTransforms, @@ -125,6 +130,14 @@ function createTransformContext( }, removeIdentifier({ content }) { ;(context.identifiers[content] as number)-- + }, + hoist(exp) { + context.hoists.push(exp) + return createExpression( + `_hoisted_${context.hoists.length}`, + false, + exp.loc + ) } } return context @@ -135,6 +148,7 @@ export function transform(root: RootNode, options: TransformOptions) { traverseChildren(root, context) root.imports = [...context.imports] root.statements = context.statements + root.hoists = context.hoists } export function traverseChildren( diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index fa58cb946..a28a9e1ea 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -108,6 +108,7 @@ function buildProps( props: PropsExpression directives: DirectiveNode[] } { + let isStatic = true let properties: ObjectExpression['properties'] = [] const mergeArgs: PropsExpression[] = [] const runtimeDirectives: DirectiveNode[] = [] @@ -130,6 +131,7 @@ function buildProps( ) } else { // directives + isStatic = false const { name, arg, exp, loc } = prop // special case for v-bind and v-on with no argument const isBind = name === 'bind' @@ -208,6 +210,11 @@ function buildProps( ) } + // hoist the object if it's fully static + if (isStatic) { + propsExpression = context.hoist(propsExpression) + } + return { props: propsExpression, directives: runtimeDirectives @@ -233,10 +240,8 @@ function dedupeProperties(properties: Property[]): Property[] { const name = prop.key.content const existing = knownProps[name] if (existing) { - if (name.startsWith('on')) { + if (name.startsWith('on') || name === 'style') { mergeAsArray(existing, prop) - } else if (name === 'style') { - mergeStyles(existing, prop) } else if (name === 'class') { mergeClasses(existing, prop) } @@ -260,25 +265,9 @@ function mergeAsArray(existing: Property, incoming: Property) { } } -// Merge dynamic and static style into a single prop -export function mergeStyles(existing: Property, incoming: Property) { - if ( - existing.value.type === NodeTypes.JS_OBJECT_EXPRESSION && - incoming.value.type === NodeTypes.JS_OBJECT_EXPRESSION - ) { - // if both are objects, merge the object expressions. - // style="color: red" :style="{ a: b }" - // -> { color: "red", a: b } - existing.value.properties.push(...incoming.value.properties) - } else { - // otherwise merge as array - // style="color:red" :style="a" - // -> style: [{ color: "red" }, a] - mergeAsArray(existing, incoming) - } -} - // Merge dynamic and static class into a single prop +// :class="expression" class="string" +// -> class: expression + "string" function mergeClasses(existing: Property, incoming: Property) { const e = existing.value as ExpressionNode const children = @@ -289,8 +278,6 @@ function mergeClasses(existing: Property, incoming: Property) { children: undefined } ]) - // :class="expression" class="string" - // -> class: expression + "string" children.push(` + " " + `, incoming.value as ExpressionNode) } diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 224438fa6..be09c6904 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -28,12 +28,11 @@ export const transformExpression: NodeTransform = (node, context) => { if (prop.arg && !prop.arg.isStatic) { if (prop.name === 'class') { // TODO special expression optimization for classes + processExpression(prop.arg, context) } else { processExpression(prop.arg, context) } } - } else if (prop.name === 'style') { - // TODO parse inline CSS literals into objects } } } diff --git a/packages/compiler-core/src/transforms/transformStyle.ts b/packages/compiler-core/src/transforms/transformStyle.ts new file mode 100644 index 000000000..d2c10db2b --- /dev/null +++ b/packages/compiler-core/src/transforms/transformStyle.ts @@ -0,0 +1,37 @@ +import { NodeTransform } from '../transform' +import { NodeTypes, createExpression } from '../ast' + +// prase inline CSS strings for static style attributes into an object +export const transformStyle: NodeTransform = (node, context) => { + if (node.type === NodeTypes.ELEMENT) { + node.props.forEach((p, i) => { + if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) { + // replace p with an expression node + const parsed = JSON.stringify(parseInlineCSS(p.value.content)) + const exp = context.hoist(createExpression(parsed, false, p.loc)) + node.props[i] = { + type: NodeTypes.DIRECTIVE, + name: `bind`, + arg: createExpression(`style`, true, p.loc), + exp, + modifiers: [], + loc: p.loc + } + } + }) + } +} + +const listDelimiterRE = /;(?![^(]*\))/g +const propertyDelimiterRE = /:(.+)/ + +function parseInlineCSS(cssText: string): Record { + const res: Record = {} + cssText.split(listDelimiterRE).forEach(function(item) { + if (item) { + const tmp = item.split(propertyDelimiterRE) + tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) + } + }) + return res +}