test: compiler integration tests

This commit is contained in:
Evan You 2019-09-25 22:29:37 -04:00
parent b5d21aeff7
commit ac7587fdb5
11 changed files with 382 additions and 251 deletions

View File

@ -154,12 +154,11 @@ return function render() {
`; `;
exports[`compiler: codegen module mode preamble 1`] = ` exports[`compiler: codegen module mode preamble 1`] = `
"import { helperOne, helperTwo } from 'vue' "import { helperOne, helperTwo } from \\"vue\\"
export default function render() { export default function render() {
with (this) { const _ctx = this
return null return null
}
}" }"
`; `;

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`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) {
@ -13,10 +13,50 @@ return function render() {
ok ok
? _createVNode(\\"div\\", 0, \\"yes\\") ? _createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\", : \\"no\\",
_renderList(list, (i, j) => { _renderList(list, (value, index) => {
return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(i + j))]) return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(value + index))])
}) })
]) ])
} }
}" }"
`; `;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
"const { createVNode, toString, renderList } = Vue
return function render() {
const _ctx = this
return createVNode(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar
}, [
toString(_ctx.world),
(_ctx.ok)
? createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, toString(value + index))])
})
])
}"
`;
exports[`compiler: integration tests module mode 1`] = `
"import { createVNode, toString, renderList } from \\"vue\\"
export default function render() {
const _ctx = this
return createVNode(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar
}, [
_toString(_ctx.world),
(_ctx.ok)
? createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
_renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, _toString(value + index))])
})
])
}"
`;

View File

@ -51,7 +51,7 @@ describe('compiler: codegen', () => {
imports: [`helperOne`, `helperTwo`] imports: [`helperOne`, `helperTwo`]
}) })
const { code } = generate(root, { mode: 'module' }) const { code } = generate(root, { mode: 'module' })
expect(code).toMatch(`import { helperOne, helperTwo } from 'vue'`) expect(code).toMatch(`import { helperOne, helperTwo } from "vue"`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -1,139 +1,227 @@
import { compile } from '../src' import { compile } from '../src'
import { SourceMapConsumer, RawSourceMap } from 'source-map' import { SourceMapConsumer, RawSourceMap } from 'source-map'
// Integration tests for parser + transform + codegen describe('compiler: integration tests', () => {
test('function mode', async () => {
const source = ` const source = `
<div id="foo" :class="bar"> <div id="foo" :class="bar">
{{ world }} {{ world }}
<div v-if="ok">yes</div> <div v-if="ok">yes</div>
<template v-else>no</template> <template v-else>no</template>
<div v-for="(i, j) in list"><span>{{ i + j }}</span></div> <div v-for="(value, index) in list"><span>{{ value + index }}</span></div>
</div> </div>
`.trim() `.trim()
const { code, map } = compile(source, {
sourceMap: true, function getPositionInCode(code: string, token: string) {
filename: `foo.vue` const generatedOffset = code.indexOf(token)
let line = 1
let lastNewLinePos = -1
for (let i = 0; i < generatedOffset; i++) {
if (code.charCodeAt(i) === 10 /* newline char code */) {
line++
lastNewLinePos = i
}
}
return {
line,
column:
lastNewLinePos === -1
? generatedOffset
: generatedOffset - lastNewLinePos - 1
}
}
test('function mode', async () => {
const { code, map } = compile(source, {
sourceMap: true,
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])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
).toMatchObject(getPositionInCode(source, `id`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `class:`))
).toMatchObject(getPositionInCode(source, `class=`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `bar`))
).toMatchObject(getPositionInCode(source, `bar`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `world`))
).toMatchObject(getPositionInCode(source, `{{ world }}`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`))
).toMatchObject(getPositionInCode(source, `ok`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `list`))
).toMatchObject(getPositionInCode(source, `list`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value`))
).toMatchObject(getPositionInCode(source, `value`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `index`))
).toMatchObject(getPositionInCode(source, `index`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`))
).toMatchObject(getPositionInCode(source, `{{ value + index }}`))
}) })
expect(code).toMatchSnapshot() test('function mode w/ prefixIdentifiers: true', async () => {
expect(map!.sources).toEqual([`foo.vue`]) const { code, map } = compile(source, {
expect(map!.sourcesContent).toEqual([source]) sourceMap: true,
filename: `foo.vue`,
const consumer = await new SourceMapConsumer(map as RawSourceMap) prefixIdentifiers: true
// id=
expect(
consumer.originalPositionFor({
line: 6,
column: 6
}) })
).toMatchObject({
line: 1, expect(code).toMatch(`const { createVNode, toString, renderList } = Vue`)
column: 5
expect(code).toMatchSnapshot()
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
).toMatchObject(getPositionInCode(source, `id`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
).toMatchObject(getPositionInCode(source, `"foo"`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `class:`))
).toMatchObject(getPositionInCode(source, `class=`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `bar`))
).toMatchObject(getPositionInCode(source, `bar`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`))
).toMatchObject(getPositionInCode(source, `bar`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `world`))
).toMatchObject(getPositionInCode(source, `{{ world }}`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.world`))
).toMatchObject(getPositionInCode(source, `{{ world }}`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`))
).toMatchObject(getPositionInCode(source, `ok`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`))
).toMatchObject(getPositionInCode(source, `ok`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `list`))
).toMatchObject(getPositionInCode(source, `list`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`))
).toMatchObject(getPositionInCode(source, `list`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value`))
).toMatchObject(getPositionInCode(source, `value`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `index`))
).toMatchObject(getPositionInCode(source, `index`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`))
).toMatchObject(getPositionInCode(source, `value + index`))
}) })
// "foo" test('module mode', async () => {
expect( const { code, map } = compile(source, {
consumer.originalPositionFor({ mode: 'module',
line: 6, sourceMap: true,
column: 10 filename: `foo.vue`
}) })
).toMatchObject({
line: 1,
column: 8
})
// :class= expect(code).toMatch(
expect( `import { createVNode, toString, renderList } from "vue"`
consumer.originalPositionFor({ )
line: 7,
column: 6
})
).toMatchObject({
line: 1,
column: 15
})
// bar
expect(
consumer.originalPositionFor({
line: 7,
column: 13
})
).toMatchObject({
line: 1,
column: 22
})
// {{ world }} expect(code).toMatchSnapshot()
expect( expect(map!.sources).toEqual([`foo.vue`])
consumer.originalPositionFor({ expect(map!.sourcesContent).toEqual([source])
line: 9,
column: 16
})
).toMatchObject({
line: 2,
column: 2
})
// ok const consumer = await new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor({
line: 10,
column: 6
})
).toMatchObject({
line: 3,
column: 13
})
// i expect(
expect( consumer.originalPositionFor(getPositionInCode(code, `id`))
consumer.originalPositionFor({ ).toMatchObject(getPositionInCode(source, `id`))
line: 13,
column: 25
})
).toMatchObject({
line: 5,
column: 15
})
// j expect(
expect( consumer.originalPositionFor(getPositionInCode(code, `"foo"`))
consumer.originalPositionFor({ ).toMatchObject(getPositionInCode(source, `"foo"`))
line: 13,
column: 28
})
).toMatchObject({
line: 5,
column: 18
})
// list expect(
expect( consumer.originalPositionFor(getPositionInCode(code, `class:`))
consumer.originalPositionFor({ ).toMatchObject(getPositionInCode(source, `class=`))
line: 13,
column: 18
})
).toMatchObject({
line: 5,
column: 24
})
// i + j expect(
expect( consumer.originalPositionFor(getPositionInCode(code, `bar`))
consumer.originalPositionFor({ ).toMatchObject(getPositionInCode(source, `bar`))
line: 14, expect(
column: 81 consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`))
}) ).toMatchObject(getPositionInCode(source, `bar`))
).toMatchObject({
line: 5, expect(
column: 36 consumer.originalPositionFor(getPositionInCode(code, `world`))
).toMatchObject(getPositionInCode(source, `{{ world }}`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.world`))
).toMatchObject(getPositionInCode(source, `{{ world }}`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`))
).toMatchObject(getPositionInCode(source, `ok`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`))
).toMatchObject(getPositionInCode(source, `ok`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `list`))
).toMatchObject(getPositionInCode(source, `list`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`))
).toMatchObject(getPositionInCode(source, `list`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value`))
).toMatchObject(getPositionInCode(source, `value`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `index`))
).toMatchObject(getPositionInCode(source, `index`))
expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`))
).toMatchObject(getPositionInCode(source, `value + index`))
}) })
}) })
test.todo('function mode w/ prefixIdentifiers: true')
test.todo('module mode')
test.todo('module mode w/ prefixIdentifiers: true')

View File

@ -29,26 +29,17 @@ function parseWithExpressionTransform(
describe('compiler: expression transform', () => { describe('compiler: expression transform', () => {
test('interpolation (root)', () => { test('interpolation (root)', () => {
const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode
expect(node.children).toMatchObject([ expect(node.children).toBeUndefined()
`_ctx.`, expect(node.content).toBe(`_ctx.foo`)
{
content: `foo`,
loc: node.loc
}
])
}) })
test('interpolation (children)', () => { test('interpolation (children)', () => {
const node = parseWithExpressionTransform( const el = parseWithExpressionTransform(
`<div>{{ foo }}</div>` `<div>{{ foo }}</div>`
) as ElementNode ) as ElementNode
expect((node.children[0] as ExpressionNode).children).toMatchObject([ const node = el.children[0] as ExpressionNode
`_ctx.`, expect(node.children).toBeUndefined()
{ expect(node.content).toBe(`_ctx.foo`)
content: `foo`,
loc: node.children[0].loc
}
])
}) })
test('directive value', () => { test('directive value', () => {
@ -57,13 +48,8 @@ describe('compiler: expression transform', () => {
) as ElementNode ) as ElementNode
expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined() expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined()
const exp = (node.props[0] as DirectiveNode).exp! const exp = (node.props[0] as DirectiveNode).exp!
expect(exp.children).toMatchObject([ expect(exp.children).toBeUndefined()
`_ctx.`, expect(exp.content).toBe(`_ctx.baz`)
{
content: `baz`,
loc: exp.loc
}
])
}) })
test('dynamic directive arg', () => { test('dynamic directive arg', () => {
@ -72,20 +58,10 @@ describe('compiler: expression transform', () => {
) as ElementNode ) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg! const arg = (node.props[0] as DirectiveNode).arg!
const exp = (node.props[0] as DirectiveNode).exp! const exp = (node.props[0] as DirectiveNode).exp!
expect(arg.children).toMatchObject([ expect(arg.children).toBeUndefined()
`_ctx.`, expect(arg.content).toBe(`_ctx.arg`)
{ expect(exp.children).toBeUndefined()
content: `arg`, expect(exp.content).toBe(`_ctx.baz`)
loc: arg.loc
}
])
expect(exp.children).toMatchObject([
`_ctx.`,
{
content: `baz`,
loc: exp.loc
}
])
}) })
test('should prefix complex expressions', () => { test('should prefix complex expressions', () => {
@ -94,9 +70,8 @@ describe('compiler: expression transform', () => {
) as ExpressionNode ) as ExpressionNode
// should parse into compound expression // should parse into compound expression
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`_ctx.`,
{ {
content: `foo`, content: `_ctx.foo`,
loc: { loc: {
source: `foo`, source: `foo`,
start: { start: {
@ -111,9 +86,9 @@ describe('compiler: expression transform', () => {
} }
} }
}, },
`(_ctx.`, `(`,
{ {
content: `baz`, content: `_ctx.baz`,
loc: { loc: {
source: `baz`, source: `baz`,
start: { start: {
@ -128,9 +103,9 @@ describe('compiler: expression transform', () => {
} }
} }
}, },
` + 1, { key: _ctx.`, ` + 1, { key: `,
{ {
content: `kuz`, content: `_ctx.kuz`,
loc: { loc: {
source: `kuz`, source: `kuz`,
start: { start: {
@ -151,17 +126,16 @@ describe('compiler: expression transform', () => {
test('should prefix v-if condition', () => { test('should prefix v-if condition', () => {
const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode
expect(node.branches[0].condition!.children).toMatchObject([ expect(node.branches[0].condition!.children).toBeUndefined()
`_ctx.`, expect(node.branches[0].condition!.content).toBe(`_ctx.ok`)
{ content: `ok` }
])
}) })
test('should prefix v-for source', () => { test('should prefix v-for source', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div v-for="i in list"/>` `<div v-for="i in list"/>`
) as ForNode ) as ForNode
expect(node.source.children).toMatchObject([`_ctx.`, { content: `list` }]) expect(node.source.children).toBeUndefined()
expect(node.source.content).toBe(`_ctx.list`)
}) })
test('should not prefix v-for alias', () => { test('should not prefix v-for alias', () => {
@ -177,7 +151,8 @@ describe('compiler: expression transform', () => {
const j = div.children[1] as ExpressionNode const j = div.children[1] as ExpressionNode
expect(j.type).toBe(NodeTypes.EXPRESSION) expect(j.type).toBe(NodeTypes.EXPRESSION)
expect(j.children).toMatchObject([`_ctx.`, { content: `j` }]) expect(j.children).toBeUndefined()
expect(j.content).toBe(`_ctx.j`)
}) })
test('should not prefix v-for aliases (multiple)', () => { test('should not prefix v-for aliases (multiple)', () => {
@ -188,12 +163,19 @@ describe('compiler: expression transform', () => {
const exp = div.children[0] as ExpressionNode const exp = div.children[0] as ExpressionNode
expect(exp.type).toBe(NodeTypes.EXPRESSION) expect(exp.type).toBe(NodeTypes.EXPRESSION)
expect(exp.content).toBe(`i + j + k`) // parsed for better source-map support
expect(exp.children).toBeUndefined() expect(exp.children).toMatchObject([
{ content: `i` },
` + `,
{ content: `j` },
` + `,
{ content: `k` }
])
const l = div.children[1] as ExpressionNode const l = div.children[1] as ExpressionNode
expect(l.type).toBe(NodeTypes.EXPRESSION) expect(l.type).toBe(NodeTypes.EXPRESSION)
expect(l.children).toMatchObject([`_ctx.`, { content: `l` }]) expect(l.children).toBeUndefined()
expect(l.content).toBe(`_ctx.l`)
}) })
test('should prefix id outside of v-for', () => { test('should prefix id outside of v-for', () => {
@ -202,8 +184,8 @@ describe('compiler: expression transform', () => {
) as ElementNode ) as ElementNode
const exp = node.children[1] as ExpressionNode const exp = node.children[1] as ExpressionNode
expect(exp.type).toBe(NodeTypes.EXPRESSION) expect(exp.type).toBe(NodeTypes.EXPRESSION)
expect(exp.content).toBe(`i`) expect(exp.children).toBeUndefined()
expect(exp.children).toMatchObject([`_ctx.`, { content: `i` }]) expect(exp.content).toBe(`_ctx.i`)
}) })
test('nested v-for', () => { test('nested v-for', () => {
@ -217,7 +199,11 @@ describe('compiler: expression transform', () => {
const innerExp = (innerFor.children[0] as ElementNode) const innerExp = (innerFor.children[0] as ElementNode)
.children[0] as ExpressionNode .children[0] as ExpressionNode
expect(innerExp.type).toBe(NodeTypes.EXPRESSION) expect(innerExp.type).toBe(NodeTypes.EXPRESSION)
expect(innerExp.children).toMatchObject([`i + _ctx.`, { content: `j` }]) expect(innerExp.children).toMatchObject([
{ content: 'i' },
` + `,
{ content: `_ctx.j` }
])
// when an inner v-for shadows a variable of an outer v-for and exit, // when an inner v-for shadows a variable of an outer v-for and exit,
// it should not cause the outer v-for's alias to be removed from known ids // it should not cause the outer v-for's alias to be removed from known ids
@ -232,8 +218,12 @@ describe('compiler: expression transform', () => {
`{{ Math.max(1, 2) }}` `{{ Math.max(1, 2) }}`
) as ExpressionNode ) as ExpressionNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.type).toBe(NodeTypes.EXPRESSION)
expect(node.content).toBe(`Math.max(1, 2)`) expect(node.children).toMatchObject([
expect(node.children).toBeUndefined() { content: `Math` },
`.`,
{ content: `max` },
`(1, 2)`
])
}) })
test('should not prefix id of a function declaration', () => { test('should not prefix id of a function declaration', () => {
@ -242,8 +232,10 @@ describe('compiler: expression transform', () => {
) as ExpressionNode ) as ExpressionNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.type).toBe(NodeTypes.EXPRESSION)
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`function foo() { return _ctx.`, `function `,
{ content: `bar` }, { content: `foo` },
`() { return `,
{ content: `_ctx.bar` },
` }` ` }`
]) ])
}) })
@ -254,8 +246,11 @@ describe('compiler: expression transform', () => {
) as ExpressionNode ) as ExpressionNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.type).toBe(NodeTypes.EXPRESSION)
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`foo => foo + _ctx.`, { content: `foo` },
{ content: `bar` } ` => `,
{ content: `foo` },
` + `,
{ content: `_ctx.bar` }
]) ])
}) })
@ -265,8 +260,8 @@ describe('compiler: expression transform', () => {
) as ExpressionNode ) as ExpressionNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.type).toBe(NodeTypes.EXPRESSION)
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`{ foo: _ctx.`, `{ foo: `,
{ content: `bar` }, { content: `_ctx.bar` },
` }` ` }`
]) ])
}) })
@ -277,10 +272,10 @@ describe('compiler: expression transform', () => {
) as ExpressionNode ) as ExpressionNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.type).toBe(NodeTypes.EXPRESSION)
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`{ [_ctx.`, `{ [`,
{ content: `foo` }, { content: `_ctx.foo` },
`]: _ctx.`, `]: `,
{ content: `bar` }, { content: `_ctx.bar` },
` }` ` }`
]) ])
}) })
@ -288,8 +283,8 @@ describe('compiler: expression transform', () => {
test('should prefix object property shorthand value', () => { test('should prefix object property shorthand value', () => {
const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`{ foo: _ctx.`, `{ foo: `,
{ content: `foo` }, { content: `_ctx.foo` },
` }` ` }`
]) ])
}) })
@ -299,9 +294,11 @@ describe('compiler: expression transform', () => {
`{{ foo.bar.baz }}` `{{ foo.bar.baz }}`
) as ExpressionNode ) as ExpressionNode
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`_ctx.`, { content: `_ctx.foo` },
{ content: `foo` }, `.`,
`.bar.baz` { content: `bar` },
`.`,
{ content: `baz` }
]) ])
}) })
@ -310,12 +307,11 @@ describe('compiler: expression transform', () => {
`{{ foo[bar][baz] }}` `{{ foo[bar][baz] }}`
) as ExpressionNode ) as ExpressionNode
expect(node.children).toMatchObject([ expect(node.children).toMatchObject([
`_ctx.`, { content: `_ctx.foo` },
{ content: `foo` }, `[`,
`[_ctx.`, { content: `_ctx.bar` },
{ content: `bar` }, `][`,
`][_ctx.`, { content: '_ctx.baz' },
{ content: 'baz' },
`]` `]`
]) ])
}) })

View File

@ -25,7 +25,7 @@ function parseWithVOn(
return ast.children[0] as ElementNode return ast.children[0] as ElementNode
} }
describe('compiler: transform v-bind', () => { describe('compiler: transform v-on', () => {
test('basic', () => { test('basic', () => {
const node = parseWithVOn(`<div v-on:click="onClick"/>`) const node = parseWithVOn(`<div v-on:click="onClick"/>`)
const props = node.codegenNode!.arguments[1] as ObjectExpression const props = node.codegenNode!.arguments[1] as ObjectExpression
@ -76,8 +76,8 @@ describe('compiler: transform v-bind', () => {
const props = node.codegenNode!.arguments[1] as ObjectExpression const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `"on" + event`, isStatic: false,
isStatic: false children: [`"on" + `, { content: `event` }]
}, },
value: { value: {
content: `handler`, content: `handler`,
@ -94,10 +94,10 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
isStatic: false, isStatic: false,
children: [`"on" + `, `_ctx.`, { content: `event` }] children: [`"on" + `, { content: `_ctx.event` }]
}, },
value: { value: {
content: `handler`, content: `_ctx.handler`,
isStatic: false isStatic: false
} }
}) })

View File

@ -150,6 +150,7 @@ export function generate(
const context = createCodegenContext(ast, options) const context = createCodegenContext(ast, options)
const { mode, push, prefixIdentifiers, indent, deindent, newline } = context const { mode, push, prefixIdentifiers, indent, deindent, newline } = context
const hasImports = ast.imports.length const hasImports = ast.imports.length
const useWithBlock = !prefixIdentifiers && mode !== 'module'
// preambles // preambles
if (mode === 'function') { if (mode === 'function') {
@ -170,7 +171,7 @@ export function generate(
} else { } else {
// generate import statements for helpers // generate import statements for helpers
if (hasImports) { if (hasImports) {
push(`import { ${ast.imports.join(', ')} } from 'vue'\n`) push(`import { ${ast.imports.join(', ')} } from "vue"\n`)
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
push(`export default `) push(`export default `)
@ -180,12 +181,12 @@ export function generate(
push(`function render() {`) push(`function render() {`)
indent() indent()
if (!prefixIdentifiers) { if (useWithBlock) {
push(`with (this) {`) push(`with (this) {`)
indent() indent()
// function mode const declarations should be inside with block // function mode const declarations should be inside with block
// also they should be renamed to avoid collision with user properties // also they should be renamed to avoid collision with user properties
if (mode === 'function' && 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()
} }
@ -206,10 +207,12 @@ export function generate(
// generate the VNode tree expression // generate the VNode tree expression
push(`return `) push(`return `)
genChildren(ast.children, context, true) genChildren(ast.children, context, true)
if (!prefixIdentifiers) {
if (useWithBlock) {
deindent() deindent()
push(`}`) push(`}`)
} }
deindent() deindent()
push(`}`) push(`}`)
return { return {

View File

@ -70,7 +70,8 @@ export const enum ErrorCodes {
X_V_ON_NO_EXPRESSION, X_V_ON_NO_EXPRESSION,
// generic errors // generic errors
X_PREFIX_ID_NOT_SUPPORTED X_PREFIX_ID_NOT_SUPPORTED,
X_MODULE_MODE_NOT_SUPPORTED
} }
export const errorMessages: { [code: number]: string } = { export const errorMessages: { [code: number]: string } = {
@ -138,5 +139,6 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`, [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`,
// generic errors // generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler because it is optimized for payload size.` [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
} }

View File

@ -18,15 +18,21 @@ export function compile(
template: string | RootNode, template: string | RootNode,
options: CompilerOptions = {} options: CompilerOptions = {}
): CodegenResult { ): CodegenResult {
if (__BROWSER__ && options.prefixIdentifiers === false) { if (__BROWSER__) {
;(options.onError || defaultOnError)( const onError = options.onError || defaultOnError
createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED) if (options.prefixIdentifiers === true) {
) onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
} else if (options.mode === 'module') {
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
} }
const ast = isString(template) ? parse(template, options) : template const ast = isString(template) ? parse(template, options) : template
const prefixIdentifiers = !__BROWSER__ && options.prefixIdentifiers === true const prefixIdentifiers =
!__BROWSER__ &&
(options.prefixIdentifiers === true || options.mode === 'module')
transform(ast, { transform(ast, {
...options, ...options,
prefixIdentifiers, prefixIdentifiers,

View File

@ -12,7 +12,7 @@ import { parseScript } from 'meriyah'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { NodeTransform, TransformContext } from '../transform' import { NodeTransform, TransformContext } from '../transform'
import { NodeTypes, createExpression, ExpressionNode } from '../ast' import { NodeTypes, createExpression, ExpressionNode } from '../ast'
import { Node, Function, Identifier } from 'estree' import { Node, Function, Identifier, Property } from 'estree'
import { advancePositionWithClone } from '../utils' import { advancePositionWithClone } from '../utils'
export const transformExpression: NodeTransform = (node, context) => { export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) { if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
@ -43,12 +43,15 @@ const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
const isFunction = (node: Node): node is Function => const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type) /Function(Expression|Declaration)$/.test(node.type)
const isPropertyKey = (node: Node, parent: Node) =>
parent.type === 'Property' && parent.key === node && !parent.computed
// cache node requires // cache node requires
let _parseScript: typeof parseScript let _parseScript: typeof parseScript
let _walk: typeof walk let _walk: typeof walk
interface PrefixMeta { interface PrefixMeta {
prefix: string prefix?: string
start: number start: number
end: number end: number
} }
@ -72,7 +75,7 @@ export function processExpression(
// fast path if expression is a simple identifier. // fast path if expression is a simple identifier.
if (simpleIdRE.test(node.content)) { if (simpleIdRE.test(node.content)) {
if (!context.identifiers[node.content]) { if (!context.identifiers[node.content]) {
node.children = [`_ctx.`, createExpression(node.content, false, node.loc)] node.content = `_ctx.${node.content}`
} }
return return
} }
@ -92,23 +95,23 @@ export function processExpression(
walk(ast, { walk(ast, {
enter(node: Node & PrefixMeta, parent) { enter(node: Node & PrefixMeta, parent) {
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
if ( if (!ids.includes(node)) {
!ids.includes(node) && if (!knownIds[node.name] && shouldPrefix(node, parent)) {
!knownIds[node.name] && if (
shouldPrefix(node, parent) isPropertyKey(node, parent) &&
) { (parent as Property).value === node
if ( ) {
parent.type === 'Property' && // property shorthand like { foo }, we need to add the key since we
parent.value === node && // rewrite the value
parent.key === node node.prefix = `${node.name}: `
) { }
// property shorthand like { foo }, we need to add the key since we node.name = `_ctx.${node.name}`
// rewrite the value ids.push(node)
node.prefix = `${node.name}: _ctx.` } else if (!isPropertyKey(node, parent)) {
} else { // also generate sub-expressioms for other identifiers for better
node.prefix = `_ctx.` // source map support. (except for property keys which are static)
ids.push(node)
} }
ids.push(node)
} }
} else if (isFunction(node)) { } else if (isFunction(node)) {
// walk function expressions and add its arguments to known identifiers // walk function expressions and add its arguments to known identifiers
@ -147,7 +150,9 @@ export function processExpression(
ids.forEach((id, i) => { ids.forEach((id, i) => {
const last = ids[i - 1] as any const last = ids[i - 1] as any
const leadingText = full.slice(last ? last.end - 1 : 0, id.start - 1) const leadingText = full.slice(last ? last.end - 1 : 0, id.start - 1)
children.push(leadingText + id.prefix) if (leadingText.length || id.prefix) {
children.push(leadingText + (id.prefix || ``))
}
const source = full.slice(id.start - 1, id.end - 1) const source = full.slice(id.start - 1, id.end - 1)
children.push( children.push(
createExpression(id.name, false, { createExpression(id.name, false, {
@ -191,12 +196,9 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
) && ) &&
// not a key of Property // not a key of Property
!( !(
parent.type === 'Property' && isPropertyKey(identifier, parent) &&
parent.key === identifier &&
// computed keys should be prefixed
!parent.computed &&
// shorthand keys should be prefixed // shorthand keys should be prefixed
!(parent.value === identifier) !((parent as Property).value === identifier)
) && ) &&
// not a property of a MemberExpression // not a property of a MemberExpression
!( !(

View File

@ -2,7 +2,6 @@ import { DirectiveTransform } from '../transform'
import { createObjectProperty, createExpression, ExpressionNode } from '../ast' import { createObjectProperty, createExpression, ExpressionNode } from '../ast'
import { capitalize } from '@vue/shared' import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { isSimpleIdentifier } from '../utils'
// v-on without arg is handled directly in ./element.ts due to it affecting // v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on // codegen for the entire props object. This transform here is only for v-on
@ -14,22 +13,18 @@ export const transformOn: DirectiveTransform = (
if (!exp && !modifiers.length) { if (!exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)) context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
} }
const { content, children, isStatic, loc: argLoc } = arg! const { content, isStatic, loc: argLoc } = arg!
let eventName: ExpressionNode let eventName: ExpressionNode
if (isStatic) { if (isStatic) {
// static arg // static arg
eventName = createExpression(`on${capitalize(content)}`, true, argLoc) eventName = createExpression(`on${capitalize(content)}`, true, argLoc)
} else if (!children) {
// dynamic arg with no rewrite
eventName = createExpression(
`"on" + ${isSimpleIdentifier(content) ? content : `(${content})`}`,
false,
argLoc
)
} else { } else {
// dynamic arg with ctx prefixing // dynamic arg. turn it into a compound expression.
eventName = arg! eventName = arg!
children.unshift(`"on" + `) ;(
eventName.children ||
(eventName.children = [{ ...eventName, children: undefined }])
).unshift(`"on" + `)
} }
// TODO .once modifier handling since it is platform agnostic // TODO .once modifier handling since it is platform agnostic
// other modifiers are handled in compiler-dom // other modifiers are handled in compiler-dom