Merge remote-tracking branch 'github/master' into changing_unwrap_ref

# Conflicts:
#	packages/reactivity/src/ref.ts
#	packages/runtime-core/__tests__/apiTemplateRef.spec.ts
#	packages/runtime-core/src/apiWatch.ts
This commit is contained in:
pikax
2020-04-08 21:21:04 +01:00
339 changed files with 26645 additions and 8965 deletions

View File

@@ -2,8 +2,8 @@
exports[`compiler: codegen ArrayExpression 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return [
foo,
bar(baz)
@@ -14,22 +14,18 @@ return function render() {
exports[`compiler: codegen CacheExpression 1`] = `
"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
export function render(_ctx, _cache) {
return _cache[1] || (_cache[1] = foo)
}"
`;
exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
export function render(_ctx, _cache) {
return _cache[1] || (
setBlockTracking(-1),
_setBlockTracking(-1),
_cache[1] = foo,
setBlockTracking(1),
_setBlockTracking(1),
_cache[1]
)
}"
@@ -37,8 +33,8 @@ export default function render() {
exports[`compiler: codegen ConditionalExpression 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return ok
? foo()
: orNot
@@ -50,8 +46,8 @@ return function render() {
exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode(\\"div\\", {
id: \\"foo\\",
[prop]: bar,
@@ -63,24 +59,17 @@ return function render() {
}"
`;
exports[`compiler: codegen SequenceExpression 1`] = `
exports[`compiler: codegen assets + temps 1`] = `
"
return function render() {
with (this) {
return (foo, bar(baz))
}
}"
`;
exports[`compiler: codegen assets 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const _component_Foo = _resolveComponent(\\"Foo\\")
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
const _component_barbaz = _resolveComponent(\\"barbaz\\")
const _directive_my_dir = _resolveDirective(\\"my_dir\\")
const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\")
const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\")
let _temp0, _temp1, _temp2
return null
}
}"
@@ -88,8 +77,8 @@ return function render() {
exports[`compiler: codegen comment 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return _createCommentVNode(\\"foo\\")
}
}"
@@ -97,18 +86,18 @@ return function render() {
exports[`compiler: codegen compound expression 1`] = `
"
return function render() {
with (this) {
return _ctx.foo + _toString(bar)
return function render(_ctx, _cache) {
with (_ctx) {
return _ctx.foo + _toDisplayString(bar) + nested
}
}"
`;
exports[`compiler: codegen forNode 1`] = `
"
return function render() {
with (this) {
return (foo, bar)
return function render(_ctx, _cache) {
with (_ctx) {
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(), 1))
}
}"
`;
@@ -116,20 +105,19 @@ return function render() {
exports[`compiler: codegen function mode preamble 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, resolveDirective: _resolveDirective } = _Vue
return null
}
}"
`;
exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
"const { createVNode, resolveDirective } = Vue
"const { createVNode: _createVNode, resolveDirective: _resolveDirective } = Vue
return function render() {
const _ctx = this
return function render(_ctx, _cache) {
return null
}"
`;
@@ -139,8 +127,8 @@ exports[`compiler: codegen hoists 1`] = `
const _hoisted_1 = hello
const _hoisted_2 = { id: \\"foo\\" }
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return null
}
}"
@@ -148,44 +136,59 @@ return function render() {
exports[`compiler: codegen ifNode 1`] = `
"
return function render() {
with (this) {
return (foo, bar)
return function render(_ctx, _cache) {
with (_ctx) {
return foo
? bar
: baz
}
}"
`;
exports[`compiler: codegen interpolation 1`] = `
"
return function render() {
with (this) {
return _toString(hello)
return function render(_ctx, _cache) {
with (_ctx) {
return _toDisplayString(hello)
}
}"
`;
exports[`compiler: codegen module mode preamble 1`] = `
"import { createVNode, resolveDirective } from \\"vue\\"
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
export default function render() {
const _ctx = this
export function render(_ctx, _cache) {
return null
}"
`;
exports[`compiler: codegen prefixIdentifiers: true should inject _ctx statement 1`] = `
"
return function render() {
const _ctx = this
exports[`compiler: codegen module mode preamble w/ optimizeBindings: true 1`] = `
"import { createVNode, resolveDirective } from \\"vue\\"
// Binding optimization for webpack code-split
const _createVNode = createVNode, _resolveDirective = resolveDirective
export function render(_ctx, _cache) {
return null
}"
`;
exports[`compiler: codegen static text 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return \\"hello\\"
}
}"
`;
exports[`compiler: codegen temps 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
let _temp0, _temp1, _temp2
return null
}
}"
`;

View File

@@ -3,21 +3,23 @@
exports[`compiler: integration tests function mode 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: bar.baz
}, [
_createTextVNode(_toString(world.burn()) + \\" \\", 1 /* TEXT */),
(_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: _createBlock(_Fragment, { key: 1 }, [\\"no\\"])),
(_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
_createTextVNode(_toDisplayString(world.burn()) + \\" \\", 1 /* TEXT */),
ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
@@ -26,21 +28,22 @@ return function render() {
`;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
"const { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
"const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = Vue
return function render() {
const _ctx = this
return (openBlock(), createBlock(\\"div\\", {
return function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar.baz
}, [
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(_ctx.ok)
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
@@ -48,21 +51,22 @@ return function render() {
`;
exports[`compiler: integration tests module mode 1`] = `
"import { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
"import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList } from \\"vue\\"
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"div\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar.baz
}, [
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(_ctx.ok)
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))

View File

@@ -0,0 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
const _withId = _withScopeId(\\"test\\")
_pushScopeId(\\"test\\")
const _hoisted_1 = _createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
const _hoisted_2 = _createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
_popScopeId()
export const render = _withId(function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
_hoisted_2
]))
})"
`;
exports[`scopeId compiler support should wrap default slot 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = _withScopeId(\\"test\\")
export const render = _withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, {
default: _withId(() => [
_createVNode(\\"div\\")
]),
_: 1
}))
})"
`;
exports[`scopeId compiler support should wrap dynamic slots 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = _withScopeId(\\"test\\")
export const render = _withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [
(_ctx.ok)
? {
name: \\"foo\\",
fn: _withId(() => [
_createVNode(\\"div\\")
])
}
: undefined,
_renderList(_ctx.list, (i) => {
return {
name: i,
fn: _withId(() => [
_createVNode(\\"div\\")
])
}
})
]), 1024 /* DYNAMIC_SLOTS */))
})"
`;
exports[`scopeId compiler support should wrap named slots 1`] = `
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = _withScopeId(\\"test\\")
export const render = _withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, {
foo: _withId(({ msg }) => [
_createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
]),
bar: _withId(() => [
_createVNode(\\"div\\")
]),
_: 1
}))
})"
`;
exports[`scopeId compiler support should wrap render function 1`] = `
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = _withScopeId(\\"test\\")
export const render = _withId(function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\"))
})"
`;

View File

@@ -9,20 +9,28 @@ import {
createArrayExpression,
createCompoundExpression,
createInterpolation,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
IfCodegenNode,
ForCodegenNode,
createCacheExpression
createCacheExpression,
createTemplateLiteral,
createBlockStatement,
createIfStatement,
createAssignmentExpression,
IfConditionalExpression,
createVNodeCall,
VNodeCall,
DirectiveArguments
} from '../src'
import {
CREATE_VNODE,
TO_STRING,
TO_DISPLAY_STRING,
RESOLVE_DIRECTIVE,
helperNameMap,
RESOLVE_COMPONENT,
CREATE_COMMENT
CREATE_COMMENT,
FRAGMENT,
RENDER_LIST
} from '../src/runtimeHelpers'
import { createElementWithCodegen } from './testUtils'
import { PatchFlags } from '@vue/shared'
@@ -37,6 +45,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
imports: [],
hoists: [],
cached: 0,
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
...options
@@ -49,11 +58,33 @@ describe('compiler: codegen', () => {
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
})
const { code } = generate(root, { mode: 'module' })
expect(code).toMatch(
`import { ${helperNameMap[CREATE_VNODE]} as _${
helperNameMap[CREATE_VNODE]
}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${
helperNameMap[RESOLVE_DIRECTIVE]
} } from "vue"`
)
expect(code).toMatchSnapshot()
})
test('module mode preamble w/ optimizeBindings: true', () => {
const root = createRoot({
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
})
const { code } = generate(root, { mode: 'module', optimizeBindings: true })
expect(code).toMatch(
`import { ${helperNameMap[CREATE_VNODE]}, ${
helperNameMap[RESOLVE_DIRECTIVE]
} } from "vue"`
)
expect(code).toMatch(
`const _${helperNameMap[CREATE_VNODE]} = ${
helperNameMap[CREATE_VNODE]
}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${
helperNameMap[RESOLVE_DIRECTIVE]
}`
)
expect(code).toMatchSnapshot()
})
@@ -83,17 +114,20 @@ describe('compiler: codegen', () => {
})
expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch(
`const { ${helperNameMap[CREATE_VNODE]}, ${
`const { ${helperNameMap[CREATE_VNODE]}: _${
helperNameMap[CREATE_VNODE]
}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${
helperNameMap[RESOLVE_DIRECTIVE]
} } = Vue`
)
expect(code).toMatchSnapshot()
})
test('assets', () => {
test('assets + temps', () => {
const root = createRoot({
components: [`Foo`, `bar-baz`, `barbaz`],
directives: [`my_dir`]
directives: [`my_dir_0`, `my_dir_1`],
temps: 3
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(
@@ -110,10 +144,16 @@ describe('compiler: codegen', () => {
}("barbaz")\n`
)
expect(code).toMatch(
`const _directive_my_dir = _${
`const _directive_my_dir_0 = _${
helperNameMap[RESOLVE_DIRECTIVE]
}("my_dir")\n`
}("my_dir_0")\n`
)
expect(code).toMatch(
`const _directive_my_dir_1 = _${
helperNameMap[RESOLVE_DIRECTIVE]
}("my_dir_1")\n`
)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
})
@@ -138,9 +178,12 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot()
})
test('prefixIdentifiers: true should inject _ctx statement', () => {
const { code } = generate(createRoot(), { prefixIdentifiers: true })
expect(code).toMatch(`const _ctx = this\n`)
test('temps', () => {
const root = createRoot({
temps: 3
})
const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
})
@@ -164,7 +207,7 @@ describe('compiler: codegen', () => {
codegenNode: createInterpolation(`hello`, locStub)
})
)
expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`)
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot()
})
@@ -193,11 +236,15 @@ describe('compiler: codegen', () => {
type: NodeTypes.INTERPOLATION,
loc: locStub,
content: createSimpleExpression(`bar`, false, locStub)
}
},
// nested compound
createCompoundExpression([` + `, `nested`])
])
})
)
expect(code).toMatch(`return _ctx.foo + _${helperNameMap[TO_STRING]}(bar)`)
expect(code).toMatch(
`return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`
)
expect(code).toMatchSnapshot()
})
@@ -208,14 +255,15 @@ describe('compiler: codegen', () => {
type: NodeTypes.IF,
loc: locStub,
branches: [],
codegenNode: createSequenceExpression([
codegenNode: createConditionalExpression(
createSimpleExpression('foo', false),
createSimpleExpression('bar', false)
]) as IfCodegenNode
createSimpleExpression('bar', false),
createSimpleExpression('baz', false)
) as IfConditionalExpression
}
})
)
expect(code).toMatch(`return (foo, bar)`)
expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
expect(code).toMatchSnapshot()
})
@@ -230,21 +278,30 @@ describe('compiler: codegen', () => {
keyAlias: undefined,
objectIndexAlias: undefined,
children: [],
codegenNode: createSequenceExpression([
createSimpleExpression('foo', false),
createSimpleExpression('bar', false)
]) as ForCodegenNode
parseResult: {} as any,
codegenNode: {
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
isBlock: true,
isForBlock: true,
props: undefined,
children: createCallExpression(RENDER_LIST),
patchFlag: '1',
dynamicProps: undefined,
directives: undefined,
loc: locStub
} as ForCodegenNode
}
})
)
expect(code).toMatch(`return (foo, bar)`)
expect(code).toMatch(`openBlock(true)`)
expect(code).toMatchSnapshot()
})
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
const { code } = generate(
createRoot({
codegenNode: createElementWithCodegen([
codegenNode: createElementWithCodegen(
// string
`"div"`,
// ObjectExpression
@@ -275,7 +332,7 @@ describe('compiler: codegen', () => {
),
// ChildNode[]
[
createElementWithCodegen([
createElementWithCodegen(
`"p"`,
createObjectExpression(
[
@@ -287,11 +344,11 @@ describe('compiler: codegen', () => {
],
locStub
)
])
)
],
// flag
PatchFlags.FULL_PROPS + ''
])
)
})
)
expect(code).toMatch(`
@@ -321,19 +378,6 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot()
})
test('SequenceExpression', () => {
const { code } = generate(
createRoot({
codegenNode: createSequenceExpression([
createSimpleExpression(`foo`, false),
createCallExpression(`bar`, [`baz`])
])
})
)
expect(code).toMatch(`return (foo, bar(baz))`)
expect(code).toMatchSnapshot()
})
test('ConditionalExpression', () => {
const { code } = generate(
createRoot({
@@ -372,7 +416,6 @@ describe('compiler: codegen', () => {
prefixIdentifiers: true
}
)
expect(code).toMatch(`const _cache = _ctx.$cache`)
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot()
})
@@ -392,17 +435,309 @@ describe('compiler: codegen', () => {
prefixIdentifiers: true
}
)
expect(code).toMatch(`const _cache = _ctx.$cache`)
expect(code).toMatch(
`
_cache[1] || (
setBlockTracking(-1),
_setBlockTracking(-1),
_cache[1] = foo,
setBlockTracking(1),
_setBlockTracking(1),
_cache[1]
)
`.trim()
)
expect(code).toMatchSnapshot()
})
test('TemplateLiteral', () => {
const { code } = generate(
createRoot({
codegenNode: createCallExpression(`_push`, [
createTemplateLiteral([
`foo`,
createCallExpression(`_renderAttr`, ['id', 'foo']),
`bar`
])
])
}),
{ ssr: true, mode: 'module' }
)
expect(code).toMatchInlineSnapshot(`
"
export function ssrRender(_ctx, _push, _parent) {
_push(\`foo\${_renderAttr(id, foo)}bar\`)
}"
`)
})
describe('IfStatement', () => {
test('if', () => {
const { code } = generate(
createRoot({
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`ok`)])
)
])
}),
{ ssr: true, mode: 'module' }
)
expect(code).toMatchInlineSnapshot(`
"
export function ssrRender(_ctx, _push, _parent) {
if (foo) {
ok()
}
}"
`)
})
test('if/else', () => {
const { code } = generate(
createRoot({
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]),
createBlockStatement([createCallExpression('bar')])
)
])
}),
{ ssr: true, mode: 'module' }
)
expect(code).toMatchInlineSnapshot(`
"
export function ssrRender(_ctx, _push, _parent) {
if (foo) {
foo()
} else {
bar()
}
}"
`)
})
test('if/else-if', () => {
const { code } = generate(
createRoot({
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]),
createIfStatement(
createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)])
)
)
])
}),
{ ssr: true, mode: 'module' }
)
expect(code).toMatchInlineSnapshot(`
"
export function ssrRender(_ctx, _push, _parent) {
if (foo) {
foo()
} else if (bar) {
bar()
}
}"
`)
})
test('if/else-if/else', () => {
const { code } = generate(
createRoot({
codegenNode: createBlockStatement([
createIfStatement(
createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]),
createIfStatement(
createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)]),
createBlockStatement([createCallExpression('baz')])
)
)
])
}),
{ ssr: true, mode: 'module' }
)
expect(code).toMatchInlineSnapshot(`
"
export function ssrRender(_ctx, _push, _parent) {
if (foo) {
foo()
} else if (bar) {
bar()
} else {
baz()
}
}"
`)
})
})
test('AssignmentExpression', () => {
const { code } = generate(
createRoot({
codegenNode: createAssignmentExpression(
createSimpleExpression(`foo`, false),
createSimpleExpression(`bar`, false)
)
})
)
expect(code).toMatchInlineSnapshot(`
"
return function render(_ctx, _cache) {
with (_ctx) {
return foo = bar
}
}"
`)
})
describe('VNodeCall', () => {
function genCode(node: VNodeCall) {
return generate(
createRoot({
codegenNode: node
})
).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1]
}
const mockProps = createObjectExpression([
createObjectProperty(`foo`, createSimpleExpression(`bar`, true))
])
const mockChildren = createCompoundExpression(['children'])
const mockDirs = createArrayExpression([
createArrayExpression([`foo`, createSimpleExpression(`bar`, false)])
]) as DirectiveArguments
test('tag only', () => {
expect(genCode(createVNodeCall(null, `"div"`))).toMatchInlineSnapshot(`
"return _createVNode(\\"div\\")
"
`)
expect(genCode(createVNodeCall(null, FRAGMENT))).toMatchInlineSnapshot(`
"return _createVNode(_Fragment)
"
`)
})
test('with props', () => {
expect(genCode(createVNodeCall(null, `"div"`, mockProps)))
.toMatchInlineSnapshot(`
"return _createVNode(\\"div\\", { foo: \\"bar\\" })
"
`)
})
test('with children, no props', () => {
expect(genCode(createVNodeCall(null, `"div"`, undefined, mockChildren)))
.toMatchInlineSnapshot(`
"return _createVNode(\\"div\\", null, children)
"
`)
})
test('with children + props', () => {
expect(genCode(createVNodeCall(null, `"div"`, mockProps, mockChildren)))
.toMatchInlineSnapshot(`
"return _createVNode(\\"div\\", { foo: \\"bar\\" }, children)
"
`)
})
test('with patchFlag and no children/props', () => {
expect(genCode(createVNodeCall(null, `"div"`, undefined, undefined, '1')))
.toMatchInlineSnapshot(`
"return _createVNode(\\"div\\", null, null, 1)
"
`)
})
test('as block', () => {
expect(
genCode(
createVNodeCall(
null,
`"div"`,
mockProps,
mockChildren,
undefined,
undefined,
undefined,
true
)
)
).toMatchInlineSnapshot(`
"return (_openBlock(), _createBlock(\\"div\\", { foo: \\"bar\\" }, children))
"
`)
})
test('as for block', () => {
expect(
genCode(
createVNodeCall(
null,
`"div"`,
mockProps,
mockChildren,
undefined,
undefined,
undefined,
true,
true
)
)
).toMatchInlineSnapshot(`
"return (_openBlock(true), _createBlock(\\"div\\", { foo: \\"bar\\" }, children))
"
`)
})
test('with directives', () => {
expect(
genCode(
createVNodeCall(
null,
`"div"`,
mockProps,
mockChildren,
undefined,
undefined,
mockDirs
)
)
).toMatchInlineSnapshot(`
"return _withDirectives(_createVNode(\\"div\\", { foo: \\"bar\\" }, children), [
[foo, bar]
])
"
`)
})
test('block + directives', () => {
expect(
genCode(
createVNodeCall(
null,
`"div"`,
mockProps,
mockChildren,
undefined,
undefined,
mockDirs,
true
)
)
).toMatchInlineSnapshot(`
"return _withDirectives((_openBlock(), _createBlock(\\"div\\", { foo: \\"bar\\" }, children)), [
[foo, bar]
])
"
`)
})
})
})

View File

@@ -44,7 +44,7 @@ describe('compiler: integration tests', () => {
return res
}
test('function mode', async () => {
test('function mode', () => {
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`
@@ -54,7 +54,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
@@ -109,7 +109,7 @@ describe('compiler: integration tests', () => {
).toMatchObject(getPositionInCode(source, `value + index`))
})
test('function mode w/ prefixIdentifiers: true', async () => {
test('function mode w/ prefixIdentifiers: true', () => {
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`,
@@ -120,7 +120,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
@@ -184,7 +184,7 @@ describe('compiler: integration tests', () => {
).toMatchObject(getPositionInCode(source, `value + index`))
})
test('module mode', async () => {
test('module mode', () => {
const { code, map } = compile(source, {
mode: 'module',
sourceMap: true,
@@ -195,7 +195,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))

View File

@@ -1,5 +1,5 @@
import { ParserOptions } from '../src/options'
import { parse, TextModes } from '../src/parse'
import { baseParse, TextModes } from '../src/parse'
import { ErrorCodes } from '../src/errors'
import {
CommentNode,
@@ -16,7 +16,7 @@ import {
describe('compiler: parse', () => {
describe('Text', () => {
test('simple text', () => {
const ast = parse('some text')
const ast = baseParse('some text')
const text = ast.children[0] as TextNode
expect(text).toStrictEqual({
@@ -31,7 +31,7 @@ describe('compiler: parse', () => {
})
test('simple text with invalid end tag', () => {
const ast = parse('some text</div>', {
const ast = baseParse('some text</div>', {
onError: () => {}
})
const text = ast.children[0] as TextNode
@@ -48,7 +48,7 @@ describe('compiler: parse', () => {
})
test('text with interpolation', () => {
const ast = parse('some {{ foo + bar }} text')
const ast = baseParse('some {{ foo + bar }} text')
const text1 = ast.children[0] as TextNode
const text2 = ast.children[2] as TextNode
@@ -73,7 +73,7 @@ describe('compiler: parse', () => {
})
test('text with interpolation which has `<`', () => {
const ast = parse('some {{ a<b && c>d }} text')
const ast = baseParse('some {{ a<b && c>d }} text')
const text1 = ast.children[0] as TextNode
const text2 = ast.children[2] as TextNode
@@ -98,7 +98,7 @@ describe('compiler: parse', () => {
})
test('text with mix of tags and interpolations', () => {
const ast = parse('some <span>{{ foo < bar + foo }} text</span>')
const ast = baseParse('some <span>{{ foo < bar + foo }} text</span>')
const text1 = ast.children[0] as TextNode
const text2 = (ast.children[1] as ElementNode).children![1] as TextNode
@@ -123,7 +123,7 @@ describe('compiler: parse', () => {
})
test('lonly "<" don\'t separate nodes', () => {
const ast = parse('a < b', {
const ast = baseParse('a < b', {
onError: err => {
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
throw err
@@ -144,7 +144,7 @@ describe('compiler: parse', () => {
})
test('lonly "{{" don\'t separate nodes', () => {
const ast = parse('a {{ b', {
const ast = baseParse('a {{ b', {
onError: error => {
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
throw error
@@ -166,7 +166,7 @@ describe('compiler: parse', () => {
test('HTML entities compatibility in text (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
const spy = jest.fn()
const ast = parse('&ampersand;', {
const ast = baseParse('&ampersand;', {
namedCharacterReferences: { amp: '&' },
onError: spy
})
@@ -195,7 +195,7 @@ describe('compiler: parse', () => {
test('HTML entities compatibility in attribute (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
const spy = jest.fn()
const ast = parse(
const ast = baseParse(
'<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>',
{
namedCharacterReferences: { amp: '&', 'amp;': '&' },
@@ -248,7 +248,7 @@ describe('compiler: parse', () => {
test('Some control character reference should be replaced.', () => {
const spy = jest.fn()
const ast = parse('&#x86;', { onError: spy })
const ast = baseParse('&#x86;', { onError: spy })
const text = ast.children[0] as TextNode
expect(text).toStrictEqual({
@@ -275,7 +275,7 @@ describe('compiler: parse', () => {
describe('Interpolation', () => {
test('simple interpolation', () => {
const ast = parse('{{message}}')
const ast = baseParse('{{message}}')
const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
@@ -300,7 +300,7 @@ describe('compiler: parse', () => {
})
test('it can have tag-like notation', () => {
const ast = parse('{{ a<b }}')
const ast = baseParse('{{ a<b }}')
const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
@@ -325,7 +325,7 @@ describe('compiler: parse', () => {
})
test('it can have tag-like notation (2)', () => {
const ast = parse('{{ a<b }}{{ c>d }}')
const ast = baseParse('{{ a<b }}{{ c>d }}')
const interpolation1 = ast.children[0] as InterpolationNode
const interpolation2 = ast.children[1] as InterpolationNode
@@ -371,7 +371,7 @@ describe('compiler: parse', () => {
})
test('it can have tag-like notation (3)', () => {
const ast = parse('<div>{{ "</div>" }}</div>')
const ast = baseParse('<div>{{ "</div>" }}</div>')
const element = ast.children[0] as ElementNode
const interpolation = element.children[0] as InterpolationNode
@@ -398,7 +398,7 @@ describe('compiler: parse', () => {
})
test('custom delimiters', () => {
const ast = parse('<p>{msg}</p>', {
const ast = baseParse('<p>{msg}</p>', {
delimiters: ['{', '}']
})
const element = ast.children[0] as ElementNode
@@ -428,7 +428,7 @@ describe('compiler: parse', () => {
describe('Comment', () => {
test('empty comment', () => {
const ast = parse('<!---->')
const ast = baseParse('<!---->')
const comment = ast.children[0] as CommentNode
expect(comment).toStrictEqual({
@@ -443,7 +443,7 @@ describe('compiler: parse', () => {
})
test('simple comment', () => {
const ast = parse('<!--abc-->')
const ast = baseParse('<!--abc-->')
const comment = ast.children[0] as CommentNode
expect(comment).toStrictEqual({
@@ -458,7 +458,7 @@ describe('compiler: parse', () => {
})
test('two comments', () => {
const ast = parse('<!--abc--><!--def-->')
const ast = baseParse('<!--abc--><!--def-->')
const comment1 = ast.children[0] as CommentNode
const comment2 = ast.children[1] as CommentNode
@@ -485,7 +485,7 @@ describe('compiler: parse', () => {
describe('Element', () => {
test('simple div', () => {
const ast = parse('<div>hello</div>')
const ast = baseParse('<div>hello</div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -516,7 +516,7 @@ describe('compiler: parse', () => {
})
test('empty', () => {
const ast = parse('<div></div>')
const ast = baseParse('<div></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -526,7 +526,6 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
isSelfClosing: false,
children: [],
loc: {
@@ -538,7 +537,7 @@ describe('compiler: parse', () => {
})
test('self closing', () => {
const ast = parse('<div/>after')
const ast = baseParse('<div/>after')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -560,7 +559,7 @@ describe('compiler: parse', () => {
})
test('void element', () => {
const ast = parse('<img>after', {
const ast = baseParse('<img>after', {
isVoidTag: tag => tag === 'img'
})
const element = ast.children[0] as ElementNode
@@ -583,8 +582,26 @@ describe('compiler: parse', () => {
})
})
test('template element with directives', () => {
const ast = baseParse('<template v-if="ok"></template>')
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.TEMPLATE
})
})
test('template element without directives', () => {
const ast = baseParse('<template></template>')
const element = ast.children[0]
expect(element).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.ELEMENT
})
})
test('native element with `isNativeTag`', () => {
const ast = parse('<div></div><comp></comp><Comp></Comp>', {
const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
isNativeTag: tag => tag === 'div'
})
@@ -608,7 +625,7 @@ describe('compiler: parse', () => {
})
test('native element without `isNativeTag`', () => {
const ast = parse('<div></div><comp></comp><Comp></Comp>')
const ast = baseParse('<div></div><comp></comp><Comp></Comp>')
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
@@ -629,8 +646,57 @@ describe('compiler: parse', () => {
})
})
test('v-is without `isNativeTag`', () => {
const ast = baseParse(
`<div></div><div v-is="'foo'"></div><Comp></Comp>`,
{
isNativeTag: tag => tag === 'div'
}
)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
tagType: ElementTypes.ELEMENT
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
tagType: ElementTypes.COMPONENT
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
tagType: ElementTypes.COMPONENT
})
})
test('v-is with `isNativeTag`', () => {
const ast = baseParse(`<div></div><div v-is="'foo'"></div><Comp></Comp>`)
expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
tagType: ElementTypes.ELEMENT
})
expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'div',
tagType: ElementTypes.COMPONENT
})
expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'Comp',
tagType: ElementTypes.COMPONENT
})
})
test('custom element', () => {
const ast = parse('<div></div><comp></comp>', {
const ast = baseParse('<div></div><comp></comp>', {
isNativeTag: tag => tag === 'div',
isCustomElement: tag => tag === 'comp'
})
@@ -649,7 +715,7 @@ describe('compiler: parse', () => {
})
test('attribute with no value', () => {
const ast = parse('<div id></div>')
const ast = baseParse('<div id></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -682,7 +748,7 @@ describe('compiler: parse', () => {
})
test('attribute with empty value, double quote', () => {
const ast = parse('<div id=""></div>')
const ast = baseParse('<div id=""></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -723,7 +789,7 @@ describe('compiler: parse', () => {
})
test('attribute with empty value, single quote', () => {
const ast = parse("<div id=''></div>")
const ast = baseParse("<div id=''></div>")
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -764,7 +830,7 @@ describe('compiler: parse', () => {
})
test('attribute with value, double quote', () => {
const ast = parse('<div id=">\'"></div>')
const ast = baseParse('<div id=">\'"></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -805,7 +871,7 @@ describe('compiler: parse', () => {
})
test('attribute with value, single quote', () => {
const ast = parse("<div id='>\"'></div>")
const ast = baseParse("<div id='>\"'></div>")
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -846,7 +912,7 @@ describe('compiler: parse', () => {
})
test('attribute with value, unquoted', () => {
const ast = parse('<div id=a/></div>')
const ast = baseParse('<div id=a/></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -887,7 +953,7 @@ describe('compiler: parse', () => {
})
test('multiple attributes', () => {
const ast = parse('<div id=a class="c" inert style=\'\'></div>')
const ast = baseParse('<div id=a class="c" inert style=\'\'></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@@ -974,7 +1040,7 @@ describe('compiler: parse', () => {
})
test('directive with no value', () => {
const ast = parse('<div v-if/>')
const ast = baseParse('<div v-if/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -992,7 +1058,7 @@ describe('compiler: parse', () => {
})
test('directive with value', () => {
const ast = parse('<div v-if="a"/>')
const ast = baseParse('<div v-if="a"/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1020,7 +1086,7 @@ describe('compiler: parse', () => {
})
test('directive with argument', () => {
const ast = parse('<div v-on:click/>')
const ast = baseParse('<div v-on:click/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1057,7 +1123,7 @@ describe('compiler: parse', () => {
})
test('directive with a modifier', () => {
const ast = parse('<div v-on.enter/>')
const ast = baseParse('<div v-on.enter/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1075,7 +1141,7 @@ describe('compiler: parse', () => {
})
test('directive with two modifiers', () => {
const ast = parse('<div v-on.enter.exact/>')
const ast = baseParse('<div v-on.enter.exact/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1093,7 +1159,7 @@ describe('compiler: parse', () => {
})
test('directive with argument and modifiers', () => {
const ast = parse('<div v-on:click.enter.exact/>')
const ast = baseParse('<div v-on:click.enter.exact/>')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1130,7 +1196,7 @@ describe('compiler: parse', () => {
})
test('v-bind shorthand', () => {
const ast = parse('<div :a=b />')
const ast = baseParse('<div :a=b />')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1178,7 +1244,7 @@ describe('compiler: parse', () => {
})
test('v-bind shorthand with modifier', () => {
const ast = parse('<div :a.sync=b />')
const ast = baseParse('<div :a.sync=b />')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1226,7 +1292,7 @@ describe('compiler: parse', () => {
})
test('v-on shorthand', () => {
const ast = parse('<div @a=b />')
const ast = baseParse('<div @a=b />')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1274,7 +1340,7 @@ describe('compiler: parse', () => {
})
test('v-on shorthand with modifier', () => {
const ast = parse('<div @a.enter=b />')
const ast = baseParse('<div @a.enter=b />')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1322,7 +1388,7 @@ describe('compiler: parse', () => {
})
test('v-slot shorthand', () => {
const ast = parse('<Comp #a="{ b }" />')
const ast = baseParse('<Comp #a="{ b }" />')
const directive = (ast.children[0] as ElementNode).props[0]
expect(directive).toStrictEqual({
@@ -1369,7 +1435,7 @@ describe('compiler: parse', () => {
})
test('v-pre', () => {
const ast = parse(
const ast = baseParse(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
`<div :id="foo"><Comp/>{{ bar }}</div>`
)
@@ -1451,7 +1517,7 @@ describe('compiler: parse', () => {
})
test('end tags are case-insensitive.', () => {
const ast = parse('<div>hello</DIV>after')
const ast = baseParse('<div>hello</DIV>after')
const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode
@@ -1468,14 +1534,14 @@ describe('compiler: parse', () => {
})
test('self closing single tag', () => {
const ast = parse('<div :class="{ some: condition }" />')
const ast = baseParse('<div :class="{ some: condition }" />')
expect(ast.children).toHaveLength(1)
expect(ast.children[0]).toMatchObject({ tag: 'div' })
})
test('self closing multiple tag', () => {
const ast = parse(
const ast = baseParse(
`<div :class="{ some: condition }" />\n` +
`<p v-bind:style="{ color: 'red' }"/>`
)
@@ -1488,7 +1554,7 @@ describe('compiler: parse', () => {
})
test('valid html', () => {
const ast = parse(
const ast = baseParse(
`<div :class="{ some: condition }">\n` +
` <p v-bind:style="{ color: 'red' }"/>\n` +
` <!-- a comment with <html> inside it -->\n` +
@@ -1513,11 +1579,11 @@ describe('compiler: parse', () => {
test('invalid html', () => {
expect(() => {
parse(`<div>\n<span>\n</div>\n</span>`)
}).toThrow('End tag was not found. (3:1)')
baseParse(`<div>\n<span>\n</div>\n</span>`)
}).toThrow('Element is missing end tag.')
const spy = jest.fn()
const ast = parse(`<div>\n<span>\n</div>\n</span>`, {
const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, {
onError: spy
})
@@ -1527,8 +1593,8 @@ describe('compiler: parse', () => {
code: ErrorCodes.X_MISSING_END_TAG,
loc: {
start: {
offset: 13,
line: 3,
offset: 6,
line: 2,
column: 1
}
}
@@ -1552,7 +1618,7 @@ describe('compiler: parse', () => {
})
test('parse with correct location info', () => {
const [foo, bar, but, baz] = parse(
const [foo, bar, but, baz] = baseParse(
`
foo
is {{ bar }} but {{ baz }}`.trim()
@@ -1588,7 +1654,7 @@ foo
describe('namedCharacterReferences option', () => {
test('use the given map', () => {
const ast: any = parse('&amp;&cups;', {
const ast: any = baseParse('&amp;&cups;', {
namedCharacterReferences: {
'cups;': '\u222A\uFE00' // UNION with serifs
},
@@ -1603,18 +1669,18 @@ foo
describe('whitespace management', () => {
it('should remove whitespaces at start/end inside an element', () => {
const ast = parse(`<div> <span/> </div>`)
const ast = baseParse(`<div> <span/> </div>`)
expect((ast.children[0] as ElementNode).children.length).toBe(1)
})
it('should remove whitespaces w/ newline between elements', () => {
const ast = parse(`<div/> \n <div/> \n <div/>`)
const ast = baseParse(`<div/> \n <div/> \n <div/>`)
expect(ast.children.length).toBe(3)
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
})
it('should remove whitespaces adjacent to comments', () => {
const ast = parse(`<div/> \n <!--foo--> <div/>`)
const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@@ -1622,7 +1688,7 @@ foo
})
it('should remove whitespaces w/ newline between comments and elements', () => {
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@@ -1630,7 +1696,7 @@ foo
})
it('should NOT remove whitespaces w/ newline between interpolations', () => {
const ast = parse(`{{ foo }} \n {{ bar }}`)
const ast = baseParse(`{{ foo }} \n {{ bar }}`)
expect(ast.children.length).toBe(3)
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
@@ -1641,7 +1707,7 @@ foo
})
it('should NOT remove whitespaces w/o newline between elements', () => {
const ast = parse(`<div/> <div/> <div/>`)
const ast = baseParse(`<div/> <div/> <div/>`)
expect(ast.children.length).toBe(5)
expect(ast.children.map(c => c.type)).toMatchObject([
NodeTypes.ELEMENT,
@@ -1653,7 +1719,7 @@ foo
})
it('should condense consecutive whitespaces in text', () => {
const ast = parse(` foo \n bar baz `)
const ast = baseParse(` foo \n bar baz `)
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
})
})
@@ -1833,7 +1899,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 11, line: 1, column: 12 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1846,7 +1912,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 12, line: 1, column: 13 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -1861,11 +1927,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 29, line: 1, column: 30 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 29, line: 1, column: 30 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1878,11 +1944,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -1897,7 +1963,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 21, line: 1, column: 22 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1910,7 +1976,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 14, line: 1, column: 15 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1925,7 +1991,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 12, line: 1, column: 13 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1938,7 +2004,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 13, line: 1, column: 14 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -1951,7 +2017,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -1962,7 +2028,7 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 32, line: 1, column: 33 }
loc: { offset: 0, line: 1, column: 1 }
},
{
type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
@@ -1975,7 +2041,7 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 28, line: 1, column: 29 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -1990,11 +2056,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 14, line: 1, column: 15 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 14, line: 1, column: 15 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2007,11 +2073,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2024,11 +2090,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 17, line: 1, column: 18 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 17, line: 1, column: 18 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2041,11 +2107,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 18, line: 1, column: 19 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 18, line: 1, column: 19 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2062,11 +2128,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 19, line: 1, column: 20 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 19, line: 1, column: 20 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2079,11 +2145,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2096,11 +2162,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2113,11 +2179,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2130,11 +2196,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2147,11 +2213,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 21, line: 1, column: 22 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 21, line: 1, column: 22 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2168,11 +2234,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2189,11 +2255,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2210,11 +2276,11 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -2292,7 +2358,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 27, line: 1, column: 28 }
loc: { offset: 0, line: 1, column: 1 }
}
]
},
@@ -2429,7 +2495,7 @@ foo
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 19, line: 1, column: 20 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -2594,17 +2660,6 @@ foo
]
}
],
UNKNOWN_NAMED_CHARACTER_REFERENCE: [
{
code: '<template>&unknown;</template>',
errors: [
{
type: ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE,
loc: { offset: 10, line: 1, column: 11 }
}
]
}
],
X_INVALID_END_TAG: [
{
code: '<template></div></template>',
@@ -2651,7 +2706,7 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 10, line: 1, column: 11 }
}
]
},
@@ -2660,11 +2715,11 @@ foo
errors: [
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 10, line: 1, column: 11 }
},
{
type: ErrorCodes.X_MISSING_END_TAG,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@@ -2716,7 +2771,7 @@ foo
),
() => {
const spy = jest.fn()
const ast = parse(code, {
const ast = baseParse(code, {
getNamespace: (tag, parent) => {
const ns = parent ? parent.ns : Namespaces.HTML
if (ns === Namespaces.HTML) {

View File

@@ -0,0 +1,97 @@
import { baseCompile } from '../src/compile'
import {
WITH_SCOPE_ID,
PUSH_SCOPE_ID,
POP_SCOPE_ID
} from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils'
describe('scopeId compiler support', () => {
test('should only work in module mode', () => {
expect(() => {
baseCompile(``, { scopeId: 'test' })
}).toThrow(`"scopeId" option is only supported in module mode`)
})
test('should wrap render function', () => {
const { ast, code } = baseCompile(`<div/>`, {
mode: 'module',
scopeId: 'test'
})
expect(ast.helpers).toContain(WITH_SCOPE_ID)
expect(code).toMatch(`const _withId = _withScopeId("test")`)
expect(code).toMatch(`export const render = _withId(function render(`)
expect(code).toMatchSnapshot()
})
test('should wrap default slot', () => {
const { code } = baseCompile(`<Child><div/></Child>`, {
mode: 'module',
scopeId: 'test'
})
expect(code).toMatch(`default: _withId(() => [`)
expect(code).toMatchSnapshot()
})
test('should wrap named slots', () => {
const { code } = baseCompile(
`<Child>
<template #foo="{ msg }">{{ msg }}</template>
<template #bar><div/></template>
</Child>
`,
{
mode: 'module',
scopeId: 'test'
}
)
expect(code).toMatch(`foo: _withId(({ msg }) => [`)
expect(code).toMatch(`bar: _withId(() => [`)
expect(code).toMatchSnapshot()
})
test('should wrap dynamic slots', () => {
const { code } = baseCompile(
`<Child>
<template #foo v-if="ok"><div/></template>
<template v-for="i in list" #[i]><div/></template>
</Child>
`,
{
mode: 'module',
scopeId: 'test'
}
)
expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
expect(code).toMatch(/name: i,\s+fn: _withId\(/)
expect(code).toMatchSnapshot()
})
test('should push scopeId for hoisted nodes', () => {
const { ast, code } = baseCompile(
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
{
mode: 'module',
scopeId: 'test',
hoistStatic: true
}
)
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
expect(ast.hoists.length).toBe(2)
expect(code).toMatch(
[
`_pushScopeId("test")`,
`const _hoisted_1 = _createVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED
)})`,
`const _hoisted_2 = _createVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED
)})`,
`_popScopeId()`
].join('\n')
)
expect(code).toMatchSnapshot()
})
})

View File

@@ -4,9 +4,8 @@ import {
locStub,
Namespaces,
ElementTypes,
PlainElementCodegenNode
VNodeCall
} from '../src'
import { CREATE_VNODE } from '../src/runtimeHelpers'
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
const leadingBracketRE = /^\[/
@@ -39,7 +38,11 @@ export function createObjectMatcher(obj: Record<string, any>) {
}
export function createElementWithCodegen(
args: PlainElementCodegenNode['arguments']
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps']
): ElementNode {
return {
type: NodeTypes.ELEMENT,
@@ -51,10 +54,16 @@ export function createElementWithCodegen(
props: [],
children: [],
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
loc: locStub,
callee: CREATE_VNODE,
arguments: args
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag,
dynamicProps,
directives: undefined,
isBlock: false,
isForBlock: false,
loc: locStub
}
}
}

View File

@@ -1,19 +1,17 @@
import { parse } from '../src/parse'
import { baseParse } from '../src/parse'
import { transform, NodeTransform } from '../src/transform'
import {
ElementNode,
NodeTypes,
DirectiveNode,
ExpressionNode
ExpressionNode,
VNodeCall
} from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors'
import {
TO_STRING,
OPEN_BLOCK,
CREATE_BLOCK,
TO_DISPLAY_STRING,
FRAGMENT,
RENDER_SLOT,
WITH_DIRECTIVES,
CREATE_COMMENT
} from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf'
@@ -26,7 +24,7 @@ import { PatchFlags } from '@vue/shared'
describe('compiler: transform', () => {
test('context state', () => {
const ast = parse(`<div>hello {{ world }}</div>`)
const ast = baseParse(`<div>hello {{ world }}</div>`)
// manually store call arguments because context is mutable and shared
// across calls
@@ -72,7 +70,7 @@ describe('compiler: transform', () => {
})
test('context.replaceNode', () => {
const ast = parse(`<div/><span/>`)
const ast = baseParse(`<div/><span/>`)
const plugin: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
// change the node to <p>
@@ -106,7 +104,7 @@ describe('compiler: transform', () => {
})
test('context.removeNode', () => {
const ast = parse(`<span/><div>hello</div><span/>`)
const ast = baseParse(`<span/><div>hello</div><span/>`)
const c1 = ast.children[0]
const c2 = ast.children[2]
@@ -132,7 +130,7 @@ describe('compiler: transform', () => {
})
test('context.removeNode (prev sibling)', () => {
const ast = parse(`<span/><div/><span/>`)
const ast = baseParse(`<span/><div/><span/>`)
const c1 = ast.children[0]
const c2 = ast.children[2]
@@ -159,7 +157,7 @@ describe('compiler: transform', () => {
})
test('context.removeNode (next sibling)', () => {
const ast = parse(`<span/><div/><span/>`)
const ast = baseParse(`<span/><div/><span/>`)
const c1 = ast.children[0]
const d1 = ast.children[1]
@@ -186,7 +184,7 @@ describe('compiler: transform', () => {
})
test('context.hoist', () => {
const ast = parse(`<div :id="foo"/><div :id="bar"/>`)
const ast = baseParse(`<div :id="foo"/><div :id="bar"/>`)
const hoisted: ExpressionNode[] = []
const mock: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
@@ -204,7 +202,7 @@ describe('compiler: transform', () => {
})
test('onError option', () => {
const ast = parse(`<div/>`)
const ast = baseParse(`<div/>`)
const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => {
context.onError(
@@ -225,20 +223,20 @@ describe('compiler: transform', () => {
})
test('should inject toString helper for interpolations', () => {
const ast = parse(`{{ foo }}`)
const ast = baseParse(`{{ foo }}`)
transform(ast, {})
expect(ast.helpers).toContain(TO_STRING)
expect(ast.helpers).toContain(TO_DISPLAY_STRING)
})
test('should inject createVNode and Comment for comments', () => {
const ast = parse(`<!--foo-->`)
const ast = baseParse(`<!--foo-->`)
transform(ast, {})
expect(ast.helpers).toContain(CREATE_COMMENT)
})
describe('root codegenNode', () => {
function transformWithCodegen(template: string) {
const ast = parse(template)
const ast = baseParse(template)
transform(ast, {
nodeTransforms: [
transformIf,
@@ -251,20 +249,19 @@ describe('compiler: transform', () => {
return ast
}
function createBlockMatcher(args: any[]) {
function createBlockMatcher(
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag']
) {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
arguments: args
}
]
type: NodeTypes.VNODE_CALL,
isBlock: true,
tag,
props,
children,
patchFlag
}
}
@@ -285,7 +282,7 @@ describe('compiler: transform', () => {
test('single element', () => {
const ast = transformWithCodegen(`<div/>`)
expect(ast.codegenNode).toMatchObject(createBlockMatcher([`"div"`]))
expect(ast.codegenNode).toMatchObject(createBlockMatcher(`"div"`))
})
test('root v-if', () => {
@@ -305,22 +302,8 @@ describe('compiler: transform', () => {
test('root element with custom directive', () => {
const ast = transformWithCodegen(`<div v-foo/>`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
// should wrap withDirectives() around createBlock()
callee: WITH_DIRECTIVES,
arguments: [
{ callee: CREATE_BLOCK },
{ type: NodeTypes.JS_ARRAY_EXPRESSION }
]
}
]
type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
})
})
@@ -348,15 +331,15 @@ describe('compiler: transform', () => {
test('multiple children', () => {
const ast = transformWithCodegen(`<div/><div/>`)
expect(ast.codegenNode).toMatchObject(
createBlockMatcher([
createBlockMatcher(
FRAGMENT,
`null`,
undefined,
[
{ type: NodeTypes.ELEMENT, tag: `div` },
{ type: NodeTypes.ELEMENT, tag: `div` }
],
] as any,
genFlagText(PatchFlags.STABLE_FRAGMENT)
])
)
)
})
})

View File

@@ -2,14 +2,14 @@
exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" })
const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -19,17 +19,17 @@ return function render() {
exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"p\\", null, [
_createVNode(\\"span\\"),
_createVNode(\\"span\\")
])
], -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -39,16 +39,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist nested static tree with comments 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = _createVNode(\\"div\\", null, [
_createCommentVNode(\\"comment\\")
])
], -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -58,15 +58,15 @@ return function render() {
exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"div\\")
const _hoisted_1 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_2 = _createVNode(\\"div\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1,
_hoisted_2
@@ -77,14 +77,14 @@ return function render() {
exports[`compiler: hoistStatic transform hoist simple element 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\")
const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -94,18 +94,18 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [
_withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 512 /* NEED_PATCH */), [
[_directive_foo]
])
]))
@@ -115,16 +115,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
_createVNode(\\"div\\", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
]))
}
}"
@@ -132,16 +132,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, [
_createVNode(_component_Comp)
@@ -153,16 +153,16 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { class: { foo: true } }
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */)
_createVNode(\\"span\\", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
]))
}
}"
@@ -170,19 +170,14 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\", null, [
\\"foo \\",
_toString(1),
\\" \\",
_toString(true)
])
const _hoisted_1 = _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -192,14 +187,14 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1))
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@@ -208,14 +203,12 @@ return function render() {
`;
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
"import { createVNode, createBlock, openBlock } from \\"vue\\"
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"div\\", null, [
createVNode(\\"div\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", null, [
_createVNode(\\"div\\", {
onClick: _cache[1] || (_cache[1] = $event => (_ctx.foo($event)))
})
])
@@ -226,14 +219,14 @@ export default function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(false), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
return (_openBlock(), _createBlock(\\"p\\", null, [
_createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
@@ -244,15 +237,17 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: ({ foo }) => [_toString(_ctx.foo)],
_compiled: true
default: _withCtx(({ foo }) => [
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */)
]),
_: 1
}))
}
}"
@@ -261,14 +256,14 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(false), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
return (_openBlock(), _createBlock(\\"p\\", null, [
_createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(o), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
@@ -279,12 +274,12 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(_component_Comp)
]))
@@ -295,12 +290,12 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { key: foo })
(_openBlock(), _createBlock(\\"div\\", { key: foo }))
]))
}
}"
@@ -309,10 +304,10 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"])
]))
@@ -323,12 +318,12 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
_createVNode(\\"div\\", { ref: foo }, null, 512 /* NEED_PATCH */)
]))
}
}"
@@ -337,10 +332,10 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\"))
}
}"
@@ -348,17 +343,17 @@ return function render() {
exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
const _hoisted_2 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
_hoisted_2
]))
@@ -370,24 +365,24 @@ return function render() {
exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = {
key: 0,
id: \\"foo\\"
}
const _hoisted_2 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(), ok
? _createBlock(\\"div\\", _hoisted_1, [
ok
? (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
_hoisted_2
])
: _createCommentVNode(\\"v-if\\", true))
]))
: _createCommentVNode(\\"v-if\\", true)
]))
}
}"

View File

@@ -3,11 +3,11 @@
exports[`compiler: transform text <template v-for> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_createTextVNode(\\"foo\\")
], 64 /* STABLE_FRAGMENT */))
@@ -19,11 +19,11 @@ return function render() {
exports[`compiler: transform text consecutive text 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString } = _Vue
return _toString(foo) + \\" bar \\" + _toString(baz)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz)
}
}"
`;
@@ -31,13 +31,13 @@ return function render() {
exports[`compiler: transform text consecutive text between elements 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
_createVNode(\\"div\\")
], 64 /* STABLE_FRAGMENT */))
}
@@ -47,13 +47,13 @@ return function render() {
exports[`compiler: transform text consecutive text mixed with elements 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
_createVNode(\\"div\\"),
_createTextVNode(\\"hello\\"),
_createVNode(\\"div\\")
@@ -65,11 +65,11 @@ return function render() {
exports[`compiler: transform text no consecutive text 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString } = _Vue
return _toString(foo)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(foo)
}
}"
`;
@@ -77,10 +77,10 @@ return function render() {
exports[`compiler: transform text text between elements (static) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(\\"hello\\"),
@@ -91,10 +91,9 @@ return function render() {
`;
exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
"const { toString } = Vue
"const { toDisplayString: _toDisplayString } = Vue
return function render() {
const _ctx = this
return toString(_ctx.foo) + \\" bar \\" + toString(_ctx.baz + _ctx.qux)
return function render(_ctx, _cache) {
return _toDisplayString(_ctx.foo) + \\" bar \\" + _toDisplayString(_ctx.baz + _ctx.qux)
}"
`;

View File

@@ -3,11 +3,11 @@
exports[`compiler: v-for codegen basic v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -17,11 +17,11 @@ return function render() {
exports[`compiler: v-for codegen keyed template v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(_Fragment, { key: item }, [
\\"hello\\",
_createVNode(\\"span\\")
@@ -34,11 +34,11 @@ return function render() {
exports[`compiler: v-for codegen keyed v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(\\"span\\", { key: item }))
}), 128 /* KEYED_FRAGMENT */))
}
@@ -48,11 +48,11 @@ return function render() {
exports[`compiler: v-for codegen skipped key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -62,11 +62,11 @@ return function render() {
exports[`compiler: v-for codegen skipped value & key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -76,11 +76,11 @@ return function render() {
exports[`compiler: v-for codegen skipped value 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -90,11 +90,11 @@ return function render() {
exports[`compiler: v-for codegen template v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(_Fragment, null, [
\\"hello\\",
_createVNode(\\"span\\")
@@ -107,11 +107,11 @@ return function render() {
exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -121,11 +121,11 @@ return function render() {
exports[`compiler: v-for codegen v-for on <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */))
}
@@ -135,16 +135,16 @@ return function render() {
exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
[_directive_foo]
]))
])
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
@@ -153,15 +153,15 @@ return function render() {
exports[`compiler: v-for codegen v-if + v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderList: _renderList, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(true), _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return (_openBlock(), _createBlock(\\"div\\"))
}), 256 /* UNKEYED_FRAGMENT */)
: _createCommentVNode(\\"v-if\\", true))
}), 256 /* UNKEYED_FRAGMENT */))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@@ -169,11 +169,11 @@ return function render() {
exports[`compiler: v-for codegen value + key + index 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}

View File

@@ -3,13 +3,13 @@
exports[`compiler: v-if codegen basic v-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@@ -17,17 +17,17 @@ return function render() {
exports[`compiler: v-if codegen template v-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
_createVNode(\\"div\\"),
\\"hello\\",
_createVNode(\\"p\\")
])
: _createCommentVNode(\\"v-if\\", true))
], 64 /* STABLE_FRAGMENT */))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@@ -35,13 +35,13 @@ return function render() {
exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
return function render(_ctx, _cache) {
with (_ctx) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@@ -49,13 +49,13 @@ return function render() {
exports[`compiler: v-if codegen v-if + v-else 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
: _createBlock(\\"p\\", { key: 1 }))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
}
}"
`;
@@ -63,15 +63,15 @@ return function render() {
exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: orNot
? _createBlock(\\"p\\", { key: 1 })
: _createBlock(_Fragment, { key: 2 }, [\\"fine\\"]))
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
: (_openBlock(), _createBlock(_Fragment, { key: 2 }, [\\"fine\\"], 64 /* STABLE_FRAGMENT */))
}
}"
`;
@@ -79,15 +79,15 @@ return function render() {
exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: orNot
? _createBlock(\\"p\\", { key: 1 })
: _createCommentVNode(\\"v-if\\", true))
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@@ -95,13 +95,27 @@ return function render() {
exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
return function render(_ctx, _cache) {
with (_ctx) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
exports[`compiler: v-if codegen v-if with key 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: \\"some-key\\" }))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;

View File

@@ -1,11 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
"import { createVNode, createBlock, openBlock } from \\"vue\\"
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: _ctx.model[_ctx.index],
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
@@ -15,10 +14,10 @@ export default function render() {
exports[`compiler: transform v-model compound expression 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: model[index],
\\"onUpdate:modelValue\\": $event => (model[index] = $event)
@@ -28,11 +27,10 @@ return function render() {
`;
exports[`compiler: transform v-model simple exprssion (with prefixIdentifiers) 1`] = `
"import { createVNode, createBlock, openBlock } from \\"vue\\"
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: _ctx.model,
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
@@ -42,10 +40,10 @@ export default function render() {
exports[`compiler: transform v-model simple exprssion 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
@@ -57,24 +55,23 @@ return function render() {
exports[`compiler: transform v-model with argument 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
value: model,
\\"onUpdate:value\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"value\\", \\"onUpdate:value\\"]))
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"value\\", \\"onUpdate:value\\"]))
}
}"
`;
exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
"import { createVNode, createBlock, openBlock } from \\"vue\\"
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
[_ctx.value]: _ctx.model,
[\\"onUpdate:\\" + _ctx.value]: $event => (_ctx.model = $event)
}, null, 16 /* FULL_PROPS */))
@@ -84,10 +81,10 @@ export default function render() {
exports[`compiler: transform v-model with dynamic argument 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
[value]: model,
[\\"onUpdate:\\" + value]: $event => (model = $event)

View File

@@ -3,11 +3,10 @@
exports[`compiler: v-once transform as root node 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode } = _Vue
const _cache = $cache
return _cache[1] || (
_setBlockTracking(-1),
_cache[1] = _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
@@ -21,13 +20,12 @@ return function render() {
exports[`compiler: v-once transform on component 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@@ -43,11 +41,10 @@ return function render() {
exports[`compiler: v-once transform on nested plain element 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@@ -63,11 +60,10 @@ return function render() {
exports[`compiler: v-once transform on slot outlet 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@@ -83,11 +79,10 @@ return function render() {
exports[`compiler: v-once transform with hoistStatic: true 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),

View File

@@ -1,111 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`compiler: transform component slots dynamically named slots 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
[_ctx.one]: ({ foo }) => [toString(foo), toString(_ctx.bar)],
[_ctx.two]: ({ bar }) => [toString(_ctx.foo), toString(bar)],
_compiled: true
}, 512 /* DYNAMIC_SLOTS */))
}"
`;
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
exports[`compiler: transform component slots explicit default slot 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [toString(foo), toString(_ctx.bar)],
_compiled: true
}))
return (_openBlock(), _createBlock(_component_Comp, null, {
[_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
[_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
_: 1
}, 1024 /* DYNAMIC_SLOTS */))
}"
`;
exports[`compiler: transform component slots implicit default slot 1`] = `
"const { createVNode, resolveComponent, createBlock, openBlock } = Vue
"const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
default: () => [
createVNode(\\"div\\")
],
_compiled: true
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(() => [
_createVNode(\\"div\\")
]),
_: 1
}))
}"
`;
exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
"const { toString, resolveComponent, renderList, createSlots, createVNode, createBlock, openBlock } = Vue
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
renderList(_ctx.list, (name) => {
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
_renderList(_ctx.list, (name) => {
return {
name: name,
fn: () => [toString(name)]
fn: _withCtx(() => [_toDisplayString(name)])
}
})
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}"
`;
exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
"const { toString, resolveComponent, createSlots, createVNode, createBlock, openBlock } = Vue
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
(_ctx.ok)
? {
name: \\"one\\",
fn: (props) => [toString(props)]
fn: _withCtx((props) => [_toDisplayString(props)])
}
: undefined
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}"
`;
exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
ok
? {
name: \\"one\\",
fn: () => [\\"foo\\"]
fn: _withCtx(() => [\\"foo\\"])
}
: orNot
? {
name: \\"two\\",
fn: (props) => [\\"bar\\"]
fn: _withCtx((props) => [\\"bar\\"])
}
: {
name: \\"one\\",
fn: () => [\\"baz\\"]
fn: _withCtx(() => [\\"baz\\"])
}
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}
}"
`;
@@ -113,59 +95,117 @@ return function render() {
exports[`compiler: transform component slots named slot with v-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
ok
? {
name: \\"one\\",
fn: () => [\\"hello\\"]
fn: _withCtx(() => [\\"hello\\"])
}
: undefined
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}
}"
`;
exports[`compiler: transform component slots named slots 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
"const _Vue = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
one: ({ foo }) => [toString(foo), toString(_ctx.bar)],
two: ({ bar }) => [toString(_ctx.foo), toString(bar)],
_compiled: true
}))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
one: _withCtx(() => [\\"foo\\"]),
default: _withCtx(() => [
\\"bar\\",
_createVNode(\\"span\\")
]),
_: 1
}))
}
}"
`;
exports[`compiler: transform component slots nested slots scoping 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render() {
const _ctx = this
const _component_Inner = resolveComponent(\\"Inner\\")
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [
createVNode(_component_Inner, null, {
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
_compiled: true
}, 512 /* DYNAMIC_SLOTS */),
return function render(_ctx, _cache) {
const _component_Inner = _resolveComponent(\\"Inner\\")
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [
_createVNode(_component_Inner, null, {
default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
_: 1
}, 1024 /* DYNAMIC_SLOTS */),
\\" \\",
toString(foo),
toString(_ctx.bar),
toString(_ctx.baz)
],
_compiled: true
_toDisplayString(foo),
_toDisplayString(_ctx.bar),
_toDisplayString(_ctx.baz)
]),
_: 1
}))
}"
`;
exports[`compiler: transform component slots on component dynamically named slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
[_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
_: 1
}))
}"
`;
exports[`compiler: transform component slots on component named slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
named: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
_: 1
}))
}"
`;
exports[`compiler: transform component slots on-component default slot 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
_: 1
}))
}"
`;
exports[`compiler: transform component slots template named slots 1`] = `
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
two: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
_: 1
}))
}"
`;

View File

@@ -1,18 +1,15 @@
import {
parse,
baseParse as parse,
transform,
NodeTypes,
generate,
CompilerOptions
CompilerOptions,
VNodeCall,
IfNode,
ElementNode,
ForNode
} from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
CREATE_VNODE,
WITH_DIRECTIVES,
FRAGMENT,
RENDER_LIST
} from '../../src/runtimeHelpers'
import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
import { transformIf } from '../../src/transforms/vIf'
@@ -20,6 +17,7 @@ import { transformFor } from '../../src/transforms/vFor'
import { transformBind } from '../../src/transforms/vBind'
import { transformOn } from '../../src/transforms/vOn'
import { createObjectMatcher, genFlagText } from '../testUtils'
import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared'
function transformWithHoist(template: string, options: CompilerOptions = {}) {
@@ -30,7 +28,8 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
transformIf,
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement
transformElement,
transformText
],
directiveTransforms: {
on: transformOn,
@@ -39,56 +38,43 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
...options
})
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
}
]
type: NodeTypes.VNODE_CALL,
isBlock: true
})
return {
root: ast,
args: (ast.codegenNode as any).expressions[1].arguments
}
return ast
}
describe('compiler: hoistStatic transform', () => {
test('should NOT hoist root node', () => {
// if the whole tree is static, the root still needs to be a block
// so that it's patched in optimized mode to skip children
const { root, args } = transformWithHoist(`<div/>`)
const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0)
expect(args).toEqual([`"div"`])
expect(root.codegenNode).toMatchObject({
tag: `"div"`
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist simple element', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span class="inline">hello</span></div>`
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
createObjectMatcher({ class: 'inline' }),
{
type: NodeTypes.TEXT,
content: `hello`
}
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ class: 'inline' }),
children: {
type: NodeTypes.TEXT,
content: `hello`
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -97,29 +83,24 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree', () => {
const { root, args } = transformWithHoist(
`<div><p><span/><span/></p></div>`
)
const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"p"`,
`null`,
[
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
type: NodeTypes.VNODE_CALL,
tag: `"p"`,
props: undefined,
children: [
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -132,21 +113,16 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist nested static tree with comments', () => {
const { root, args } = transformWithHoist(
`<div><div><!--comment--></div></div>`
)
const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"div"`,
`null`,
[{ type: NodeTypes.COMMENT, content: `comment` }]
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }]
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -159,20 +135,18 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist siblings with common non-hoistable parent', () => {
const { root, args } = transformWithHoist(`<div><span/><div/></div>`)
const root = transformWithHoist(`<div><span/><div/></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"div"`]
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -192,14 +166,14 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist components', () => {
const { root, args } = transformWithHoist(`<div><Comp/></div>`)
const root = transformWithHoist(`<div><Comp/></div>`)
expect(root.hoists.length).toBe(0)
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [`_component_Comp`]
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
}
])
@@ -207,22 +181,20 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist element with dynamic props', () => {
const { root, args } = transformWithHoist(`<div><div :id="foo"/></div>`)
const root = transformWithHoist(`<div><div :id="foo"/></div>`)
expect(root.hoists.length).toBe(0)
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
id: `[foo]`
}),
`null`,
genFlagText(PatchFlags.PROPS),
`["id"]`
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
id: `[foo]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["id"]`
}
}
])
@@ -230,19 +202,19 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist element with static key', () => {
const { root, args } = transformWithHoist(`<div><div key="foo"/></div>`)
const root = transformWithHoist(`<div><div key="foo"/></div>`)
expect(root.hoists.length).toBe(1)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"div"`, createObjectMatcher({ key: 'foo' })]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({ key: 'foo' })
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -251,24 +223,22 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist element with dynamic key', () => {
const { root, args } = transformWithHoist(`<div><div :key="foo"/></div>`)
const root = transformWithHoist(`<div><div :key="foo"/></div>`)
expect(root.hoists.length).toBe(0)
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
key: `[foo]`
})
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
key: `[foo]`
})
}
}
])
@@ -276,21 +246,19 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist element with dynamic ref', () => {
const { root, args } = transformWithHoist(`<div><div :ref="foo"/></div>`)
const root = transformWithHoist(`<div><div :ref="foo"/></div>`)
expect(root.hoists.length).toBe(0)
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
ref: `[foo]`
}),
`null`,
genFlagText(PatchFlags.NEED_PATCH)
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
ref: `[foo]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH)
}
}
])
@@ -298,32 +266,23 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with directives', () => {
const { root, args } = transformWithHoist(
`<div><div id="foo" v-foo/></div>`
)
const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: WITH_DIRECTIVES,
arguments: [
{
callee: CREATE_VNODE,
arguments: [
`"div"`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
`null`,
genFlagText(PatchFlags.NEED_PATCH)
]
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION
}
}
}
])
@@ -331,21 +290,19 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with dynamic text children', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><div id="foo">{{ hello }}</div></div>`
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
{ type: NodeTypes.INTERPOLATION },
genFlagText(PatchFlags.TEXT)
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { type: NodeTypes.INTERPOLATION },
patchFlag: genFlagText(PatchFlags.TEXT)
}
}
])
@@ -353,20 +310,16 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with unhoistable children', () => {
const { root, args } = transformWithHoist(
`<div><div id="foo"><Comp/></div></div>`
)
const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[{ type: NodeTypes.ELEMENT, tag: `Comp` }]
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
}
}
])
@@ -374,7 +327,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should hoist v-if props/children if static', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><div v-if="ok" id="foo"><span/></div></div>`
)
expect(root.hoists).toMatchObject([
@@ -383,37 +336,31 @@ describe('compiler: hoistStatic transform', () => {
id: 'foo'
}),
{
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
])
expect(args[2][0].codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
// blocks should NOT be hoisted
callee: CREATE_BLOCK,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[
{
codegenNode: { content: `_hoisted_2` }
}
]
]
expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
// blocks should NOT be hoisted
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [
{
codegenNode: { content: `_hoisted_2` }
}
}
]
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-for children if static', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><div v-for="i in list" id="foo"><span/></div></div>`
)
expect(root.hoists).toMatchObject([
@@ -421,55 +368,58 @@ describe('compiler: hoistStatic transform', () => {
id: 'foo'
}),
{
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
])
const forBlockCodegen = args[2][0].codegenNode
const forBlockCodegen = ((root.children[0] as ElementNode)
.children[0] as ForNode).codegenNode
expect(forBlockCodegen).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
{
callee: CREATE_BLOCK,
arguments: [
FRAGMENT,
`null`,
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST
},
genFlagText(PatchFlags.UNKEYED_FRAGMENT)
]
}
]
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
props: undefined,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST
},
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
})
const innerBlockCodegen =
forBlockCodegen.expressions[1].arguments[2].arguments[1].returns
expect(innerBlockCodegen).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
expect(innerBlockCodegen.returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [
{
callee: CREATE_BLOCK,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[
{
codegenNode: { content: `_hoisted_2` }
}
]
]
codegenNode: { content: `_hoisted_2` }
}
]
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static text node between elements', () => {
const root = transformWithHoist(`<div>static<div>static</div></div>`)
expect(root.hoists).toMatchObject([
{
callee: CREATE_TEXT,
arguments: [
{
type: NodeTypes.TEXT,
content: `static`
}
]
},
{
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
])
})
describe('prefixIdentifiers', () => {
test('hoist nested static tree with static interpolation', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
prefixIdentifiers: true
@@ -477,44 +427,18 @@ describe('compiler: hoistStatic transform', () => {
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
`null`,
[
{
type: NodeTypes.TEXT,
content: `foo `
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
isConstant: true
}
},
{
type: NodeTypes.TEXT,
content: ` `
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `true`,
isStatic: false,
isConstant: true
}
}
]
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: undefined,
children: {
type: NodeTypes.COMPOUND_EXPRESSION
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -523,12 +447,12 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree with static prop value', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
prefixIdentifiers: true
@@ -537,26 +461,23 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
createObjectMatcher({ foo: `[0]` }),
{
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
isConstant: true
}
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ foo: `[0]` }),
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
isConstant: true
}
]
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@@ -565,12 +486,12 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist class with static object value', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
prefixIdentifiers: true
@@ -596,39 +517,37 @@ describe('compiler: hoistStatic transform', () => {
]
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"span"`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isConstant: false,
isStatic: false
}
},
`1 /* TEXT */`
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isConstant: false,
isStatic: false
}
},
patchFlag: `1 /* TEXT */`
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist expressions that refer scope variables', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{
prefixIdentifiers: true
@@ -640,7 +559,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist expressions that refer scope variables (2)', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{
prefixIdentifiers: true
@@ -652,7 +571,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
prefixIdentifiers: true
@@ -664,7 +583,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist elements with cached handlers', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,

View File

@@ -0,0 +1,24 @@
import {
baseParse as parse,
transform,
ElementNode,
noopDirectiveTransform,
VNodeCall
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
describe('compiler: noop directive transform', () => {
test('should add no props to DOM', () => {
const ast = parse(`<div v-noop/>`)
transform(ast, {
nodeTransforms: [transformElement],
directiveTransforms: {
noop: noopDirectiveTransform
}
})
const node = ast.children[0] as ElementNode
// As v-noop adds no properties the codegen should be identical to
// rendering a div with no props or reactive data (so just the tag as the arg)
expect((node.codegenNode as VNodeCall).props).toBeUndefined()
})
})

View File

@@ -1,24 +1,28 @@
import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
import {
CompilerOptions,
baseParse as parse,
transform,
ErrorCodes
} from '../../src'
import {
RESOLVE_COMPONENT,
CREATE_VNODE,
MERGE_PROPS,
RESOLVE_DIRECTIVE,
WITH_DIRECTIVES,
TO_HANDLERS,
helperNameMap,
PORTAL,
TELEPORT,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
KEEP_ALIVE,
BASE_TRANSITION
} from '../../src/runtimeHelpers'
import {
CallExpression,
NodeTypes,
createObjectProperty,
DirectiveNode,
RootNode
RootNode,
VNodeCall
} from '../../src/ast'
import { transformElement } from '../../src/transforms/transformElement'
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
@@ -33,7 +37,7 @@ function parseWithElementTransform(
options: CompilerOptions = {}
): {
root: RootNode
node: CallExpression
node: VNodeCall
} {
// wrap raw template in an extra div so that it doesn't get turned into a
// block as root node
@@ -43,8 +47,8 @@ function parseWithElementTransform(
...options
})
const codegenNode = (ast as any).children[0].children[0]
.codegenNode as CallExpression
expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
.codegenNode as VNodeCall
expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
return {
root: ast,
node: codegenNode
@@ -68,63 +72,63 @@ describe('compiler: element transform', () => {
test('static props', () => {
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
id: 'foo',
class: 'bar'
})
])
}),
children: undefined
})
})
test('props + children', () => {
const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
id: 'foo'
}),
[
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
})
test('0 placeholder for children with no props', () => {
const { node } = parseWithElementTransform(`<div><span/></div>`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
`null`,
[
expect(node).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
})
test('v-bind="obj"', () => {
const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`)
// single v-bind doesn't need mergeProps
expect(root.helpers).not.toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
// should directly use `obj` in props position
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
})
@@ -135,8 +139,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" />`
)
expect(root.helpers).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -156,8 +160,8 @@ describe('compiler: element transform', () => {
`<div v-bind="obj" id="foo" />`
)
expect(root.helpers).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -177,8 +181,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" class="bar" />`
)
expect(root.helpers).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -201,8 +205,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="obj" class="bar" />`
)
expect(root.helpers).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -231,8 +235,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="handlers" v-bind="obj" />`
)
expect(root.helpers).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -259,43 +263,43 @@ describe('compiler: element transform', () => {
test('should handle plain <template> as normal element', () => {
const { node } = parseWithElementTransform(`<template id="foo" />`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"template"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"template"`,
props: createObjectMatcher({
id: 'foo'
})
])
})
})
test('should handle <Portal> with normal children', () => {
test('should handle <Teleport> with normal children', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
`<${tag} target="#foo"><span /></${tag}>`
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(PORTAL)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
PORTAL,
createObjectMatcher({
expect(root.helpers).toContain(TELEPORT)
expect(node).toMatchObject({
tag: TELEPORT,
props: createObjectMatcher({
target: '#foo'
}),
[
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
}
assert(`portal`)
assert(`Portal`)
assert(`teleport`)
assert(`Teleport`)
})
test('should handle <Suspense>', () => {
@@ -305,11 +309,11 @@ describe('compiler: element transform', () => {
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(SUSPENSE)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
SUSPENSE,
`null`,
hasFallback
expect(node).toMatchObject({
tag: SUSPENSE,
props: undefined,
children: hasFallback
? createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
@@ -317,15 +321,15 @@ describe('compiler: element transform', () => {
fallback: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
: createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
])
})
}
assert(`suspense`, `foo`)
@@ -339,18 +343,23 @@ describe('compiler: element transform', () => {
test('should handle <KeepAlive>', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
`<${tag}><span /></${tag}>`
)
const root = parse(`<div><${tag}><span /></${tag}></div>`)
transform(root, {
nodeTransforms: [transformElement, transformText]
})
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(KEEP_ALIVE)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
KEEP_ALIVE,
`null`,
const node = (root.children[0] as any).children[0].codegenNode
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: KEEP_ALIVE,
isBlock: true, // should be forced into a block
props: undefined,
// keep-alive should not compile content to slots
[{ type: NodeTypes.ELEMENT, tag: 'span' }]
])
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
// should get a dynamic slots flag to force updates
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
})
}
assert(`keep-alive`)
@@ -364,17 +373,17 @@ describe('compiler: element transform', () => {
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(BASE_TRANSITION)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
BASE_TRANSITION,
`null`,
createObjectMatcher({
expect(node).toMatchObject({
tag: BASE_TRANSITION,
props: undefined,
children: createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
])
})
}
assert(`base-transition`)
@@ -398,14 +407,13 @@ describe('compiler: element transform', () => {
foo(dir) {
_dir = dir
return {
props: [createObjectProperty(dir.arg!, dir.exp!)],
needRuntime: false
props: [createObjectProperty(dir.arg!, dir.exp!)]
}
}
}
})
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@@ -417,8 +425,8 @@ describe('compiler: element transform', () => {
})
// should factor in props returned by custom directive transforms
// in patchFlag analysis
expect(node.arguments[3]).toMatch(PatchFlags.PROPS + '')
expect(node.arguments[4]).toMatch(`"bar"`)
expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
expect(node.dynamicProps).toMatch(`"bar"`)
})
test('directiveTransform with needRuntime: true', () => {
@@ -437,20 +445,12 @@ describe('compiler: element transform', () => {
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
expect(node.callee).toBe(WITH_DIRECTIVES)
expect(node.arguments).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"div"`,
`null`,
`null`,
genFlagText(PatchFlags.NEED_PATCH) // should generate appropriate flag
]
},
{
expect(node).toMatchObject({
tag: `"div"`,
props: undefined,
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
@@ -473,7 +473,7 @@ describe('compiler: element transform', () => {
}
]
}
])
})
})
test('directiveTransform with needRuntime: Symbol', () => {
@@ -494,7 +494,7 @@ describe('compiler: element transform', () => {
expect(root.helpers).toContain(CREATE_VNODE)
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
expect(root.directives.length).toBe(0)
expect((node as any).arguments[1].elements[0].elements[0]).toBe(
expect(node.directives!.elements[0].elements[0]).toBe(
`_${helperNameMap[CREATE_VNODE]}`
)
})
@@ -508,12 +508,8 @@ describe('compiler: element transform', () => {
expect(root.directives).toContain(`bar`)
expect(root.directives).toContain(`baz`)
expect(node.callee).toBe(WITH_DIRECTIVES)
expect(node.arguments).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION
},
{
expect(node).toMatchObject({
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
@@ -583,7 +579,7 @@ describe('compiler: element transform', () => {
}
]
}
])
})
})
test(`props merging: event handlers`, () => {
@@ -595,7 +591,7 @@ describe('compiler: element transform', () => {
}
}
)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@@ -627,7 +623,7 @@ describe('compiler: element transform', () => {
test(`props merging: style`, () => {
const { node } = parseWithElementTransform(
`<div style="color: red" :style="{ color: 'red' }" />`,
`<div style="color: green" :style="{ color: 'red' }" />`,
{
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
@@ -635,7 +631,7 @@ describe('compiler: element transform', () => {
}
}
)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@@ -650,7 +646,7 @@ describe('compiler: element transform', () => {
elements: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"green"}`,
isStatic: false
},
{
@@ -674,7 +670,7 @@ describe('compiler: element transform', () => {
}
}
)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@@ -707,113 +703,175 @@ describe('compiler: element transform', () => {
describe('patchFlag analysis', () => {
test('TEXT', () => {
const { node } = parseWithBind(`<div>foo</div>`)
expect(node.arguments.length).toBe(3)
expect(node.patchFlag).toBeUndefined()
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
expect(node2.arguments.length).toBe(4)
expect(node2.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
// multiple nodes, merged with optimize text
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
expect(node3.arguments.length).toBe(4)
expect(node3.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
})
test('CLASS', () => {
const { node } = parseWithBind(`<div :class="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.CLASS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
})
test('STYLE', () => {
const { node } = parseWithBind(`<div :style="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.STYLE))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
})
test('PROPS', () => {
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
expect(node.arguments.length).toBe(5)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.PROPS))
expect(node.arguments[4]).toBe(`["foo", "baz"]`)
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
test('CLASS + STYLE + PROPS', () => {
const { node } = parseWithBind(
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
)
expect(node.arguments.length).toBe(5)
expect(node.arguments[3]).toBe(
expect(node.patchFlag).toBe(
genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
)
expect(node.arguments[4]).toBe(`["foo", "baz"]`)
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
test('FULL_PROPS (v-bind)', () => {
const { node } = parseWithBind(`<div v-bind="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('FULL_PROPS (dynamic key)', () => {
const { node } = parseWithBind(`<div :[foo]="bar" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('FULL_PROPS (w/ others)', () => {
const { node } = parseWithBind(
`<div id="foo" v-bind="bar" :class="cls" />`
)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('NEED_PATCH (static ref)', () => {
const { node } = parseWithBind(`<div ref="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
test('NEED_PATCH (dynamic ref)', () => {
const { node } = parseWithBind(`<div :ref="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
test('NEED_PATCH (custom directives)', () => {
const { node } = parseWithBind(`<div v-foo />`)
const vnodeCall = node.arguments[0] as CallExpression
expect(vnodeCall.arguments.length).toBe(4)
expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
test('HYDRATE_EVENTS', () => {
// ignore click events (has dedicated fast path)
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
directiveTransforms: {
on: transformOn
}
})
// should only have props flag
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
const { node: node2 } = parseWithElementTransform(
`<div @keyup="foo" />`,
{
directiveTransforms: {
on: transformOn
}
}
)
expect(node2.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
)
})
})
describe('dynamic component', () => {
test('static binding', () => {
const { node, root } = parseWithBind(`<component is="foo" />`)
expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node).toMatchObject({
callee: CREATE_VNODE,
arguments: ['_component_foo']
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true
}
]
}
})
})
test('dynamic binding', () => {
const { node, root } = parseWithBind(`<component :is="foo" />`)
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node.arguments).toMatchObject([
{
expect(node).toMatchObject({
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo'
},
'$'
content: 'foo',
isStatic: false
}
]
}
])
})
})
test('v-is', () => {
const { node, root } = parseWithBind(`<div v-is="'foo'" />`)
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node).toMatchObject({
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `'foo'`,
isStatic: false
}
]
},
// should skip v-is runtime check
directives: undefined
})
})
})
test('<svg> should be forced into blocks', () => {
const ast = parse(`<div><svg/></div>`)
transform(ast, {
nodeTransforms: [transformElement]
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"svg"`,
isBlock: true
})
})
// #938
test('element with dynamic keys should be forced into blocks', () => {
const ast = parse(`<div><div :key="foo" /></div>`)
transform(ast, {
nodeTransforms: [transformElement]
})
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
isBlock: true
})
})
})

View File

@@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
DirectiveNode,
@@ -317,6 +317,16 @@ describe('compiler: expression transform', () => {
})
})
test('should not duplicate object key with same name as value', () => {
const node = parseWithExpressionTransform(
`{{ { foo: foo } }}`
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
})
})
test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform(
`{{ { [foo]: bar } }}`
@@ -380,7 +390,65 @@ describe('compiler: expression transform', () => {
const onError = jest.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError })
expect(onError.mock.calls[0][0].message).toMatch(
`Invalid JavaScript expression. (1:4)`
`Error parsing JavaScript expression: Unexpected token`
)
})
describe('ES Proposals support', () => {
test('bigInt', () => {
const node = parseWithExpressionTransform(
`{{ 13000n }}`
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `13000n`,
isStatic: false,
isConstant: true
})
})
test('nullish colescing', () => {
const node = parseWithExpressionTransform(
`{{ a ?? b }}`
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }]
})
})
test('optional chaining', () => {
const node = parseWithExpressionTransform(
`{{ a?.b?.c }}`
) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `_ctx.a` },
`?.`,
{ content: `b` },
`?.`,
{ content: `c` }
]
})
})
test('Enabling additional plugins', () => {
// enabling pipeline operator to replace filters:
const node = parseWithExpressionTransform(`{{ a |> uppercase }}`, {
expressionPlugins: [
[
'pipelineOperator',
{
proposal: 'minimal'
}
]
]
}) as InterpolationNode
expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }]
})
})
})
})

View File

@@ -1,6 +1,6 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
ElementNode,
NodeTypes,

View File

@@ -1,6 +1,6 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
NodeTypes,
generate,

View File

@@ -1,11 +1,11 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
CallExpression
VNodeCall
} from '../../src'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
@@ -33,8 +33,7 @@ function parseWithVBind(
describe('compiler: transform v-bind', () => {
test('basic', () => {
const node = parseWithVBind(`<div v-bind:id="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
@@ -69,8 +68,7 @@ describe('compiler: transform v-bind', () => {
test('dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
@@ -103,8 +101,7 @@ describe('compiler: transform v-bind', () => {
test('.camel modifier', () => {
const node = parseWithVBind(`<div v-bind:foo-bar.camel="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
@@ -119,8 +116,7 @@ describe('compiler: transform v-bind', () => {
test('.camel modifier w/ dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `_${helperNameMap[CAMELIZE]}(foo)`,
@@ -137,12 +133,11 @@ describe('compiler: transform v-bind', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
children: [
`${helperNameMap[CAMELIZE]}(`,
`_${helperNameMap[CAMELIZE]}(`,
{ content: `_ctx.foo` },
`(`,
{ content: `_ctx.bar` },

View File

@@ -1,4 +1,4 @@
import { parse } from '../../src/parse'
import { baseParse as parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor'
@@ -12,19 +12,11 @@ import {
SimpleExpressionNode,
ElementNode,
InterpolationNode,
CallExpression,
SequenceExpression
ForCodegenNode
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
RENDER_LIST,
RENDER_SLOT,
WITH_DIRECTIVES
} from '../../src/runtimeHelpers'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { createObjectMatcher, genFlagText } from '../testUtils'
@@ -48,7 +40,7 @@ function parseWithForTransform(
})
return {
root: ast,
node: ast.children[0] as ForNode
node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode }
}
}
@@ -566,64 +558,40 @@ describe('compiler: v-for', () => {
describe('codegen', () => {
function assertSharedCodegen(
node: SequenceExpression,
node: ForCodegenNode,
keyed: boolean = false,
customReturn: boolean = false
) {
expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
arguments: [
FRAGMENT,
`null`,
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
arguments: [
{}, // to be asserted by each test
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: customReturn
? {}
: {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
}
]
}
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
isForBlock: true,
patchFlag: keyed
? genFlagText(PatchFlags.KEYED_FRAGMENT)
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
arguments: [
{}, // to be asserted by each test
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: customReturn
? {}
: {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
]
},
keyed
? genFlagText(PatchFlags.KEYED_FRAGMENT)
: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
]
}
]
}
]
}
})
const renderListArgs = ((node.expressions[1] as CallExpression)
.arguments[2] as CallExpression).arguments
const renderListArgs = node.children.arguments
return {
source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params,
returns: (renderListArgs[1] as any).returns,
blockArgs: customReturn
? null
: (renderListArgs[1] as any).returns.expressions[1].arguments
innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns
}
}
@@ -635,7 +603,9 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [`"span"`]
innerVNodeCall: {
tag: `"span"`
}
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -698,15 +668,16 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
FRAGMENT,
`null`,
[
innerVNodeCall: {
tag: FRAGMENT,
props: undefined,
isBlock: true,
children: [
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` }
],
genFlagText(PatchFlags.STABLE_FRAGMENT)
]
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -757,12 +728,12 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
`"span"`,
createObjectMatcher({
innerVNodeCall: {
tag: `"span"`,
props: createObjectMatcher({
key: `[item]`
})
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -777,17 +748,17 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
FRAGMENT,
createObjectMatcher({
innerVNodeCall: {
tag: FRAGMENT,
props: createObjectMatcher({
key: `[item]`
}),
[
children: [
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` }
],
genFlagText(PatchFlags.STABLE_FRAGMENT)
]
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -798,53 +769,33 @@ describe('compiler: v-for', () => {
node: { codegenNode }
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: { content: `ok` },
consequent: {
type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({
key: `[0]`
}),
isBlock: true,
isForBlock: true,
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK,
arguments: []
},
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: { content: `ok` },
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
// should optimize v-if + v-for into a single Fragment block
arguments: [
FRAGMENT,
createObjectMatcher({ key: `[0]` }),
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
arguments: [
{ content: `list` },
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: [{ content: `i` }],
returns: {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
arguments: [`"div"`]
}
]
}
}
]
},
genFlagText(PatchFlags.UNKEYED_FRAGMENT)
]
}
callee: RENDER_LIST,
arguments: [
{ content: `list` },
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: [{ content: `i` }],
returns: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
isBlock: true
}
}
]
}
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -856,18 +807,8 @@ describe('compiler: v-for', () => {
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
const { returns } = assertSharedCodegen(codegenNode, false, true)
expect(returns).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
// should wrap withDirectives() around createBlock()
{
callee: WITH_DIRECTIVES,
arguments: [
{ callee: CREATE_BLOCK },
{ type: NodeTypes.JS_ARRAY_EXPRESSION }
]
}
]
type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
})
expect(generate(root).code).toMatchSnapshot()
})

View File

@@ -1,4 +1,4 @@
import { parse } from '../../src/parse'
import { baseParse as parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement'
@@ -10,18 +10,16 @@ import {
TextNode,
CommentNode,
SimpleExpressionNode,
SequenceExpression,
ConditionalExpression,
CallExpression
IfConditionalExpression,
VNodeCall,
ElementTypes
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
MERGE_PROPS,
WITH_DIRECTIVES,
RENDER_SLOT,
CREATE_COMMENT
} from '../../src/runtimeHelpers'
@@ -43,7 +41,9 @@ function parseWithIfTransform(
}
return {
root: ast,
node: ast.children[returnIndex] as IfNode
node: ast.children[returnIndex] as IfNode & {
codegenNode: IfConditionalExpression
}
}
}
@@ -79,6 +79,22 @@ describe('compiler: v-if', () => {
expect((node.branches[0].children[2] as ElementNode).tag).toBe(`p`)
})
test('component v-if', () => {
const { node } = parseWithIfTransform(`<Component v-if="ok"></Component>`)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(
`Component`
)
expect((node.branches[0].children[0] as ElementNode).tagType).toBe(
ElementTypes.COMPONENT
)
expect(
((node.branches[0].children[0] as ElementNode)!
.codegenNode as VNodeCall)!.isBlock
).toBe(false)
})
test('v-if + v-else', () => {
const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
expect(node.type).toBe(NodeTypes.IF)
@@ -266,49 +282,49 @@ describe('compiler: v-if', () => {
describe('codegen', () => {
function assertSharedCodegen(
node: SequenceExpression,
node: IfConditionalExpression,
depth: number = 0,
hasElse: boolean = false
) {
expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK,
arguments: []
},
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `ok`
},
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
},
alternate:
depth < 1
? {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `orNot`
},
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
},
alternate: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `ok`
},
consequent: {
type: NodeTypes.VNODE_CALL,
isBlock: true
},
alternate:
depth < 1
? hasElse
? {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `orNot`
},
consequent: {
type: NodeTypes.VNODE_CALL,
isBlock: true
},
alternate: hasElse
? {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
}
}
]
: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
}
}
})
}
@@ -318,15 +334,11 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/>`)
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
})
@@ -339,20 +351,16 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
FRAGMENT,
createObjectMatcher({ key: `[0]` }),
[
expect(codegenNode.consequent).toMatchObject({
tag: FRAGMENT,
props: createObjectMatcher({ key: `[0]` }),
children: [
{ type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: 'p' }
]
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2).toMatchObject({
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
})
@@ -364,10 +372,7 @@ describe('compiler: v-if', () => {
root,
node: { codegenNode }
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
// assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
@@ -380,10 +385,7 @@ describe('compiler: v-if', () => {
root,
node: { codegenNode }
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
// assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
@@ -397,18 +399,14 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
assertSharedCodegen(codegenNode, 0, true)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2.arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
expect(codegenNode.alternate).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -418,18 +416,15 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
assertSharedCodegen(codegenNode, 1)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -441,28 +436,25 @@ describe('compiler: v-if', () => {
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
)
assertSharedCodegen(codegenNode, 1, true)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect((branch2.alternate as CallExpression).arguments).toMatchObject([
FRAGMENT,
createObjectMatcher({ key: `[2]` }),
[
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
expect(branch2.alternate).toMatchObject({
tag: FRAGMENT,
props: createObjectMatcher({ key: `[2]` }),
children: [
{
type: NodeTypes.TEXT,
content: `fine`
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
@@ -470,9 +462,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
@@ -483,9 +474,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -502,9 +492,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@@ -521,13 +510,21 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-foo />`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.callee).toBe(WITH_DIRECTIVES)
const realBranch = branch1.arguments[0] as CallExpression
expect(realBranch.arguments[1]).toMatchObject(
createObjectMatcher({ key: `[0]` })
)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined()
expect(branch1.props).toMatchObject(createObjectMatcher({ key: `[0]` }))
})
test('v-if with key', () => {
const {
root,
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" key="some-key"/>`)
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: 'some-key' })
})
expect(generate(root).code).toMatchSnapshot()
})
test.todo('with comments')

View File

@@ -1,16 +1,15 @@
import {
parse,
baseParse as parse,
transform,
generate,
ElementNode,
ObjectExpression,
CompilerOptions,
CallExpression,
ForNode,
PlainElementNode,
PlainElementCodegenNode,
ComponentNode,
NodeTypes
NodeTypes,
VNodeCall
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
@@ -43,8 +42,8 @@ describe('compiler: transform v-model', () => {
test('simple exprssion', () => {
const root = parseWithVModel('<input v-model="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -82,8 +81,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -119,8 +118,8 @@ describe('compiler: transform v-model', () => {
test('compound expression', () => {
const root = parseWithVModel('<input v-model="model[index]" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -158,8 +157,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -191,15 +190,19 @@ describe('compiler: transform v-model', () => {
children: [
'$event => (',
{
content: '_ctx.model',
isStatic: false
children: [
{
content: '_ctx.model',
isStatic: false
},
'[',
{
content: '_ctx.index',
isStatic: false
},
']'
]
},
'[',
{
content: '_ctx.index',
isStatic: false
},
']',
' = $event)'
]
}
@@ -211,9 +214,8 @@ describe('compiler: transform v-model', () => {
test('with argument', () => {
const root = parseWithVModel('<input v-model:value="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
content: 'value',
@@ -248,8 +250,8 @@ describe('compiler: transform v-model', () => {
test('with dynamic argument', () => {
const root = parseWithVModel('<input v-model:[value]="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -292,8 +294,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@@ -338,12 +340,12 @@ describe('compiler: transform v-model', () => {
})
expect(root.cached).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
.codegenNode as PlainElementCodegenNode
.codegenNode as VNodeCall
// should not list cached prop in dynamicProps
expect(codegen.arguments[4]).toBe(`["modelValue"]`)
expect(
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
).toBe(NodeTypes.JS_CACHE_EXPRESSION)
expect(codegen.dynamicProps).toBe(`["modelValue"]`)
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
NodeTypes.JS_CACHE_EXPRESSION
)
})
test('should not cache update handler if it refers v-for scope variables', () => {
@@ -356,10 +358,10 @@ describe('compiler: transform v-model', () => {
)
expect(root.cached).toBe(0)
const codegen = ((root.children[0] as ForNode)
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
.children[0] as PlainElementNode).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect(
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
(codegen.props as ObjectExpression).properties[1].value.type
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
})
@@ -371,18 +373,18 @@ describe('compiler: transform v-model', () => {
}
)
const codegen = ((root.children[0] as ComponentNode)
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
.children[0] as PlainElementNode).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
})
test('should generate modelModifers for component v-model', () => {
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
prefixIdentifiers: true
})
const args = ((root.children[0] as ComponentNode)
.codegenNode as CallExpression).arguments
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
// props
expect(args[1]).toMatchObject({
expect(vnodeCall.props).toMatchObject({
properties: [
{ key: { content: `modelValue` } },
{ key: { content: `onUpdate:modelValue` } },
@@ -394,7 +396,7 @@ describe('compiler: transform v-model', () => {
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
expect(args[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect(vnodeCall.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
})
test('should generate modelModifers for component v-model with arguments', () => {
@@ -404,10 +406,10 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
}
)
const args = ((root.children[0] as ComponentNode)
.codegenNode as CallExpression).arguments
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
// props
expect(args[1]).toMatchObject({
expect(vnodeCall.props).toMatchObject({
properties: [
{ key: { content: `foo` } },
{ key: { content: `onUpdate:foo` } },
@@ -425,7 +427,9 @@ describe('compiler: transform v-model', () => {
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
expect(args[4]).toBe(`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`)
expect(vnodeCall.dynamicProps).toBe(
`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`
)
})
describe('errors', () => {

View File

@@ -1,13 +1,12 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
NodeTypes,
CallExpression,
PlainElementCodegenNode
VNodeCall
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
@@ -31,54 +30,58 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
describe('compiler: transform v-on', () => {
test('basic', () => {
const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `onClick`,
isStatic: true,
loc: {
start: {
line: 1,
column: 11
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
content: `onClick`,
isStatic: true,
loc: {
start: {
line: 1,
column: 11
},
end: {
line: 1,
column: 16
}
}
},
end: {
line: 1,
column: 16
value: {
content: `onClick`,
isStatic: false,
loc: {
start: {
line: 1,
column: 18
},
end: {
line: 1,
column: 25
}
}
}
}
},
value: {
content: `onClick`,
isStatic: false,
loc: {
start: {
line: 1,
column: 18
},
end: {
line: 1,
column: 25
}
}
}
]
})
})
test('dynamic arg', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
isStatic: false
}
}
]
})
})
@@ -86,18 +89,20 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
}
]
})
})
@@ -105,38 +110,60 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + (`,
{ content: `_ctx.event` },
`(`,
{ content: `_ctx.foo` },
`)`,
`)`
]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + (`,
{ content: `_ctx.event` },
`(`,
{ content: `_ctx.foo` },
`)`,
`)`
]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
}
]
})
})
test('should wrap as function if expression is inline statement', () => {
const { node } = parseWithVOn(`<div @click="i++"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `i++` }, `)`]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `i++` }, `)`]
}
}
]
})
})
test('should handle multiple inline statement', () => {
const { node } = parseWithVOn(`<div @click="foo();bar()"/>`)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
// consistent with 2.x
children: [`$event => {`, { content: `foo();bar()` }, `}`]
}
}
]
})
})
@@ -144,48 +171,90 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => (`,
{ content: `_ctx.foo` },
`(`,
// should NOT prefix $event
{ content: `$event` },
`)`,
`)`
]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => (`,
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `_ctx.foo` },
`(`,
// should NOT prefix $event
{ content: `$event` },
`)`
]
},
`)`
]
}
}
]
})
})
test('multiple inline statements w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, {
prefixIdentifiers: true
})
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => {`,
{
children: [
{ content: `_ctx.foo` },
`(`,
// should NOT prefix $event
{ content: `$event` },
`);`,
{ content: `_ctx.bar` },
`()`
]
},
`}`
]
}
}
]
})
})
test('should NOT wrap as function if expression is already function expression', () => {
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `$event => foo($event)`
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `$event => foo($event)`
}
}
]
})
})
test('should NOT wrap as function if expression is complex member expression', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a['b' + c]`
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a['b' + c]`
}
}
]
})
})
@@ -193,14 +262,21 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, `['b' + `, { content: `_ctx.c` }, `]`]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `_ctx.a` },
`['b' + `,
{ content: `_ctx.c` },
`]`
]
}
}
]
})
})
@@ -208,21 +284,23 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `e` },
` => `,
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`
]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `e` },
` => `,
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`
]
}
}
]
})
})
@@ -250,6 +328,22 @@ describe('compiler: transform v-on', () => {
expect(onError).not.toHaveBeenCalled()
})
test('case conversion for vnode hooks', () => {
const { node } = parseWithVOn(`<div v-on:vnode-mounted="onMount"/>`)
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
content: `onVnodeMounted`
},
value: {
content: `onMount`
}
}
]
})
})
describe('cacheHandler', () => {
test('empty handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
@@ -257,10 +351,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
expect(root.cached).toBe(1)
const args = (node.codegenNode as PlainElementCodegenNode).arguments
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@@ -276,10 +372,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
expect(root.cached).toBe(1)
const args = (node.codegenNode as PlainElementCodegenNode).arguments
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@@ -295,10 +393,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
expect(root.cached).toBe(1)
const args = (node.codegenNode as PlainElementCodegenNode).arguments
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@@ -314,15 +414,22 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
expect(root.cached).toBe(1)
const args = (node.codegenNode as PlainElementCodegenNode).arguments
expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `_ctx.foo` }, `++`, `)`]
children: [
`$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] },
`)`
]
}
})
})

View File

@@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
NodeTypes,
generate,
@@ -7,11 +7,7 @@ import {
} from '../../src'
import { transformOnce } from '../../src/transforms/vOnce'
import { transformElement } from '../../src/transforms/transformElement'
import {
CREATE_VNODE,
RENDER_SLOT,
SET_BLOCK_TRACKING
} from '../../src/runtimeHelpers'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
import { transformBind } from '../../src/transforms/vBind'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
@@ -36,8 +32,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()
@@ -51,8 +47,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()
@@ -66,8 +62,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
})
expect(generate(root).code).toMatchSnapshot()
@@ -100,8 +96,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()

View File

@@ -1,14 +1,15 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
generate,
ElementNode,
NodeTypes,
ErrorCodes,
ForNode,
CallExpression,
ComponentNode
ComponentNode,
VNodeCall,
SlotsExpression
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@@ -46,7 +47,8 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
root: ast,
slots:
ast.children[0].type === NodeTypes.ELEMENT
? (ast.children[0].codegenNode as CallExpression).arguments[2]
? ((ast.children[0].codegenNode as VNodeCall)
.children as SlotsExpression)
: null
}
}
@@ -67,8 +69,8 @@ function createSlotMatcher(obj: Record<string, any>) {
} as any
})
.concat({
key: { content: `_compiled` },
value: { content: `true` }
key: { content: `_` },
value: { content: `1`, isStatic: false }
})
}
}
@@ -95,7 +97,7 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('explicit default slot', () => {
test('on-component default slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true }
@@ -128,7 +130,40 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('named slots', () => {
test('on component named slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true }
)
expect(slots).toMatchObject(
createSlotMatcher({
named: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`
}
}
]
}
})
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('template named slots', () => {
const { root, slots } = parseWithSlots(
`<Comp>
<template v-slot:one="{ foo }">
@@ -189,6 +224,76 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('on component dynamically named slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true }
)
expect(slots).toMatchObject(
createSlotMatcher({
'[_ctx.named]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`
}
}
]
}
})
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('named slots w/ implicit default slot', () => {
const { root, slots } = parseWithSlots(
`<Comp>
<template #one>foo</template>bar<span/>
</Comp>`
)
expect(slots).toMatchObject(
createSlotMatcher({
one: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
returns: [
{
type: NodeTypes.TEXT,
content: `foo`
}
]
},
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined,
returns: [
{
type: NodeTypes.TEXT,
content: `bar`
},
{
type: NodeTypes.ELEMENT,
tag: `span`
}
]
}
})
)
expect(generate(root).code).toMatchSnapshot()
})
test('dynamically named slots', () => {
const { root, slots } = parseWithSlots(
`<Comp>
@@ -274,43 +379,41 @@ describe('compiler: transform component slots', () => {
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
`_component_Inner`,
`null`,
createSlotMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `bar`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.baz`
}
type: NodeTypes.VNODE_CALL,
tag: `_component_Inner`,
props: undefined,
children: createSlotMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
]
}
}),
// nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot.
genFlagText(PatchFlags.DYNAMIC_SLOTS)
]
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `bar`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.baz`
}
}
]
}
}),
// nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot.
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
}
},
// test scope
@@ -351,8 +454,8 @@ describe('compiler: transform component slots', () => {
)
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any
const comp = div.arguments[2][0]
expect(comp.codegenNode.arguments[3]).toBe(
const comp = div.children[0]
expect(comp.codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.DYNAMIC_SLOTS)
)
})
@@ -364,12 +467,12 @@ describe('compiler: transform component slots', () => {
if (root.children[0].type === NodeTypes.FOR) {
const div = (root.children[0].children[0] as ElementNode)
.codegenNode as any
const comp = div.arguments[2][0]
flag = comp.codegenNode.arguments[3]
const comp = div.children[0]
flag = comp.codegenNode.patchFlag
} else {
const innerComp = (root.children[0] as ComponentNode)
.children[0] as ComponentNode
flag = (innerComp.codegenNode as CallExpression).arguments[3]
flag = (innerComp.codegenNode as VNodeCall).patchFlag
}
if (shouldForce) {
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
@@ -419,7 +522,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -443,7 +546,7 @@ describe('compiler: transform component slots', () => {
}
]
})
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + ''
)
expect(generate(root).code).toMatchSnapshot()
@@ -461,7 +564,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -491,7 +594,7 @@ describe('compiler: transform component slots', () => {
}
]
})
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + ''
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
@@ -510,7 +613,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -551,7 +654,7 @@ describe('compiler: transform component slots', () => {
}
]
})
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + ''
)
expect(generate(root).code).toMatchSnapshot()
@@ -569,7 +672,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -601,20 +704,20 @@ describe('compiler: transform component slots', () => {
}
]
})
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + ''
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
describe('errors', () => {
test('error on extraneous children w/ named slots', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = jest.fn()
const source = `<Comp><template #default>foo</template>bar</Comp>`
parseWithSlots(source, { onError })
const index = source.indexOf('bar')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
loc: {
source: `bar`,
start: {
@@ -699,28 +802,5 @@ describe('compiler: transform component slots', () => {
}
})
})
test('error on named slot on component', () => {
const onError = jest.fn()
const source = `<Comp v-slot:foo>foo</Comp>`
parseWithSlots(source, { onError })
const index = source.indexOf('v-slot')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
loc: {
source: `v-slot:foo`,
start: {
offset: index,
line: 1,
column: index + 1
},
end: {
offset: index + 10,
line: 1,
column: index + 11
}
}
})
})
})
})

View File

@@ -1,15 +1,16 @@
{
"name": "@vue/compiler-core",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.11",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
"types": "dist/compiler-core.d.ts",
"files": [
"index.js",
"dist"
],
"types": "dist/compiler-core.d.ts",
"buildOptions": {
"name": "VueCompilerCore",
"formats": [
"esm-bundler",
"cjs"
@@ -17,7 +18,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
"url": "git+https://github.com/vuejs/vue-next.git"
},
"keywords": [
"vue"
@@ -25,12 +26,14 @@
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
"url": "https://github.com/vuejs/vue-next/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
"dependencies": {
"acorn": "^7.1.0",
"@vue/shared": "3.0.0-alpha.11",
"@babel/parser": "^7.8.6",
"@babel/types": "^7.8.6",
"estree-walker": "^0.8.1",
"source-map": "^0.7.3"
"source-map": "^0.6.1"
}
}

View File

@@ -1,17 +1,17 @@
import { isString } from '@vue/shared'
import { ForParseResult } from './transforms/vFor'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RENDER_SLOT,
CREATE_SLOTS,
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT
FRAGMENT,
CREATE_VNODE,
WITH_DIRECTIVES
} from './runtimeHelpers'
import { PropsExpression } from './transforms/transformElement'
import { ImportItem } from './transform'
import { ImportItem, TransformContext } from './transform'
// Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces like SVG and MathML are declared by platform specific
@@ -38,14 +38,22 @@ export const enum NodeTypes {
FOR,
TEXT_CALL,
// codegen
VNODE_CALL,
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
JS_PROPERTY,
JS_ARRAY_EXPRESSION,
JS_FUNCTION_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
JS_CONDITIONAL_EXPRESSION,
JS_CACHE_EXPRESSION
JS_CACHE_EXPRESSION,
// ssr codegen
JS_BLOCK_STATEMENT,
JS_TEMPLATE_LITERAL,
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
JS_RETURN_STATEMENT
}
export const enum ElementTypes {
@@ -85,6 +93,7 @@ export type TemplateChildNode =
| TextNode
| CommentNode
| IfNode
| IfBranchNode
| ForNode
| TextCallNode
@@ -97,7 +106,9 @@ export interface RootNode extends Node {
hoists: JSChildNode[]
imports: ImportItem[]
cached: number
codegenNode: TemplateChildNode | JSChildNode | undefined
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
}
export type ElementNode =
@@ -114,35 +125,40 @@ export interface BaseElementNode extends Node {
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
codegenNode:
| CallExpression
| SimpleExpressionNode
| CacheExpression
| undefined
}
export interface PlainElementNode extends BaseElementNode {
tagType: ElementTypes.ELEMENT
codegenNode:
| ElementCodegenNode
| undefined
| VNodeCall
| SimpleExpressionNode // when hoisted
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: TemplateLiteral
}
export interface ComponentNode extends BaseElementNode {
tagType: ElementTypes.COMPONENT
codegenNode: ComponentCodegenNode | undefined | CacheExpression // when cached by v-once
codegenNode:
| VNodeCall
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: CallExpression
}
export interface SlotOutletNode extends BaseElementNode {
tagType: ElementTypes.SLOT
codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once
codegenNode:
| RenderSlotCall
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: CallExpression
}
export interface TemplateNode extends BaseElementNode {
tagType: ElementTypes.TEMPLATE
codegenNode: ElementCodegenNode | undefined | CacheExpression
// TemplateNode is a container type that always gets compiled away
codegenNode: undefined
}
export interface TextNode extends Node {
@@ -190,6 +206,7 @@ export interface CompoundExpressionNode extends Node {
type: NodeTypes.COMPOUND_EXPRESSION
children: (
| SimpleExpressionNode
| CompoundExpressionNode
| InterpolationNode
| TextNode
| string
@@ -202,7 +219,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode: IfCodegenNode
codegenNode?: IfConditionalExpression
}
export interface IfBranchNode extends Node {
@@ -217,28 +234,56 @@ export interface ForNode extends Node {
valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
parseResult: ForParseResult
children: TemplateChildNode[]
codegenNode: ForCodegenNode
codegenNode?: ForCodegenNode
}
export interface TextCallNode extends Node {
type: NodeTypes.TEXT_CALL
content: TextNode | InterpolationNode | CompoundExpressionNode
codegenNode: CallExpression
codegenNode: CallExpression | SimpleExpressionNode // when hoisted
}
export type TemplateTextChildNode =
| TextNode
| InterpolationNode
| CompoundExpressionNode
export interface VNodeCall extends Node {
type: NodeTypes.VNODE_CALL
tag: string | symbol | CallExpression
props: PropsExpression | undefined
children:
| TemplateChildNode[] // multiple children
| TemplateTextChildNode // single text child
| SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call
| undefined
patchFlag: string | undefined
dynamicProps: string | undefined
directives: DirectiveArguments | undefined
isBlock: boolean
isForBlock: boolean
}
// JS Node Types ---------------------------------------------------------------
// We also include a number of JavaScript AST nodes for code generation.
// The AST is an intentionally minimal subset just to meet the exact needs of
// Vue render function generation.
export type JSChildNode =
| VNodeCall
| CallExpression
| ObjectExpression
| ArrayExpression
| ExpressionNode
| FunctionExpression
| ConditionalExpression
| SequenceExpression
| CacheExpression
| AssignmentExpression
| SequenceExpression
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
@@ -247,6 +292,7 @@ export interface CallExpression extends Node {
| string
| symbol
| JSChildNode
| SSRCodegenNode
| TemplateChildNode
| TemplateChildNode[])[]
}
@@ -269,21 +315,20 @@ export interface ArrayExpression extends Node {
export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION
params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
body?: BlockStatement | IfStatement
newline: boolean
}
export interface SequenceExpression extends Node {
type: NodeTypes.JS_SEQUENCE_EXPRESSION
expressions: JSChildNode[]
// so that codegen knows it needs to generate ScopeId wrapper
isSlot: boolean
}
export interface ConditionalExpression extends Node {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
test: ExpressionNode
test: JSChildNode
consequent: JSChildNode
alternate: JSChildNode
newline: boolean
}
export interface CacheExpression extends Node {
@@ -293,59 +338,76 @@ export interface CacheExpression extends Node {
isVNode: boolean
}
// SSR-specific Node Types -----------------------------------------------------
export type SSRCodegenNode =
| BlockStatement
| TemplateLiteral
| IfStatement
| AssignmentExpression
| ReturnStatement
| SequenceExpression
export interface BlockStatement extends Node {
type: NodeTypes.JS_BLOCK_STATEMENT
body: (JSChildNode | IfStatement)[]
}
export interface TemplateLiteral extends Node {
type: NodeTypes.JS_TEMPLATE_LITERAL
elements: (string | JSChildNode)[]
}
export interface IfStatement extends Node {
type: NodeTypes.JS_IF_STATEMENT
test: ExpressionNode
consequent: BlockStatement
alternate: IfStatement | BlockStatement | ReturnStatement | undefined
}
export interface AssignmentExpression extends Node {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION
left: SimpleExpressionNode
right: JSChildNode
}
export interface SequenceExpression extends Node {
type: NodeTypes.JS_SEQUENCE_EXPRESSION
expressions: JSChildNode[]
}
export interface ReturnStatement extends Node {
type: NodeTypes.JS_RETURN_STATEMENT
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
}
// Codegen Node Types ----------------------------------------------------------
// createVNode(...)
export interface PlainElementCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // tag, props, children, patchFlag, dynamicProps
| [string | symbol]
| [string | symbol, PropsExpression]
| [string | symbol, 'null' | PropsExpression, TemplateChildNode[]]
| [
string | symbol,
'null' | PropsExpression,
'null' | TemplateChildNode[],
string
]
| [
string | symbol,
'null' | PropsExpression,
'null' | TemplateChildNode[],
string,
string
]
export interface DirectiveArguments extends ArrayExpression {
elements: DirectiveArgumentNode[]
}
export type ElementCodegenNode =
| PlainElementCodegenNode
| CodegenNodeWithDirective<PlainElementCodegenNode>
// createVNode(...)
export interface PlainComponentCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // Comp, props, slots, patchFlag, dynamicProps
| [string | symbol]
| [string | symbol, PropsExpression]
| [string | symbol, 'null' | PropsExpression, SlotsExpression]
| [
string | symbol,
'null' | PropsExpression,
'null' | SlotsExpression,
string
]
| [
string | symbol,
'null' | PropsExpression,
'null' | SlotsExpression,
string,
string
]
export interface DirectiveArgumentNode extends ArrayExpression {
elements: // dir, exp, arg, modifiers
| [string]
| [string, ExpressionNode]
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
export type ComponentCodegenNode =
| PlainComponentCodegenNode
| CodegenNodeWithDirective<PlainComponentCodegenNode>
// renderSlot(...)
export interface RenderSlotCall extends CallExpression {
callee: typeof RENDER_SLOT
arguments: // $slots, name, props, fallback
| [string, string | ExpressionNode]
| [string, string | ExpressionNode, PropsExpression]
| [
string,
string | ExpressionNode,
PropsExpression | '{}',
TemplateChildNode[]
]
}
export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
@@ -397,63 +459,20 @@ export interface DynamicSlotFnProperty extends Property {
value: SlotFunctionExpression
}
// withDirectives(createVNode(...), [
// [_directive_foo, someValue],
// [_directive_bar, someValue, "arg", { mod: true }]
// ])
export interface CodegenNodeWithDirective<T extends CallExpression>
extends CallExpression {
callee: typeof WITH_DIRECTIVES
arguments: [T, DirectiveArguments]
}
export interface DirectiveArguments extends ArrayExpression {
elements: DirectiveArgumentNode[]
}
export interface DirectiveArgumentNode extends ArrayExpression {
elements: // dir, exp, arg, modifiers
| [string]
| [string, ExpressionNode]
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
// renderSlot(...)
export interface SlotOutletCodegenNode extends CallExpression {
callee: typeof RENDER_SLOT
arguments: // $slots, name, props, fallback
| [string, string | ExpressionNode]
| [string, string | ExpressionNode, PropsExpression]
| [
string,
string | ExpressionNode,
PropsExpression | '{}',
TemplateChildNode[]
]
}
export type BlockCodegenNode =
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
export interface IfCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, IfConditionalExpression]
}
export type BlockCodegenNode = VNodeCall | RenderSlotCall
export interface IfConditionalExpression extends ConditionalExpression {
consequent: BlockCodegenNode
alternate: BlockCodegenNode | IfConditionalExpression
}
export interface ForCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, ForBlockCodegenNode]
}
export interface ForBlockCodegenNode extends CallExpression {
callee: typeof CREATE_BLOCK
arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string]
export interface ForCodegenNode extends VNodeCall {
isBlock: true
tag: typeof FRAGMENT
props: undefined
children: ForRenderListExpression
patchFlag: string
isForBlock: true
}
export interface ForRenderListExpression extends CallExpression {
@@ -465,11 +484,6 @@ export interface ForIteratorExpression extends FunctionExpression {
returns: BlockCodegenNode
}
export interface OpenBlockExpression extends CallExpression {
callee: typeof OPEN_BLOCK
arguments: []
}
// AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never
@@ -481,6 +495,63 @@ export const locStub: SourceLocation = {
end: { line: 1, column: 1, offset: 0 }
}
export function createRoot(
children: TemplateChildNode[],
loc = locStub
): RootNode {
return {
type: NodeTypes.ROOT,
children,
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined,
loc
}
}
export function createVNodeCall(
context: TransformContext | null,
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps'],
directives?: VNodeCall['directives'],
isBlock: VNodeCall['isBlock'] = false,
isForBlock: VNodeCall['isForBlock'] = false,
loc = locStub
): VNodeCall {
if (context) {
if (isBlock) {
context.helper(OPEN_BLOCK)
context.helper(CREATE_BLOCK)
} else {
context.helper(CREATE_VNODE)
}
if (directives) {
context.helper(WITH_DIRECTIVES)
}
}
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
isForBlock,
loc
}
}
export function createArrayExpression(
elements: ArrayExpression['elements'],
loc: SourceLocation = locStub
@@ -554,15 +625,9 @@ export function createCompoundExpression(
}
}
type InferCodegenNodeType<T> = T extends
| typeof CREATE_VNODE
| typeof CREATE_BLOCK
? PlainElementCodegenNode | PlainComponentCodegenNode
: T extends typeof WITH_DIRECTIVES
?
| CodegenNodeWithDirective<PlainElementCodegenNode>
| CodegenNodeWithDirective<PlainComponentCodegenNode>
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
? RenderSlotCall
: CallExpression
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
@@ -579,8 +644,9 @@ export function createCallExpression<T extends CallExpression['callee']>(
export function createFunctionExpression(
params: FunctionExpression['params'],
returns: FunctionExpression['returns'],
returns: FunctionExpression['returns'] = undefined,
newline: boolean = false,
isSlot: boolean = false,
loc: SourceLocation = locStub
): FunctionExpression {
return {
@@ -588,30 +654,23 @@ export function createFunctionExpression(
params,
returns,
newline,
isSlot,
loc
}
}
export function createSequenceExpression(
expressions: SequenceExpression['expressions']
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
loc: locStub
}
}
export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate']
alternate: ConditionalExpression['alternate'],
newline = true
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,
consequent,
alternate,
newline,
loc: locStub
}
}
@@ -629,3 +688,69 @@ export function createCacheExpression(
loc: locStub
}
}
export function createBlockStatement(
body: BlockStatement['body']
): BlockStatement {
return {
type: NodeTypes.JS_BLOCK_STATEMENT,
body,
loc: locStub
}
}
export function createTemplateLiteral(
elements: TemplateLiteral['elements']
): TemplateLiteral {
return {
type: NodeTypes.JS_TEMPLATE_LITERAL,
elements,
loc: locStub
}
}
export function createIfStatement(
test: IfStatement['test'],
consequent: IfStatement['consequent'],
alternate?: IfStatement['alternate']
): IfStatement {
return {
type: NodeTypes.JS_IF_STATEMENT,
test,
consequent,
alternate,
loc: locStub
}
}
export function createAssignmentExpression(
left: AssignmentExpression['left'],
right: AssignmentExpression['right']
): AssignmentExpression {
return {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
left,
right,
loc: locStub
}
}
export function createSequenceExpression(
expressions: SequenceExpression['expressions']
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
loc: locStub
}
}
export function createReturnStatement(
returns: ReturnStatement['returns']
): ReturnStatement {
return {
type: NodeTypes.JS_RETURN_STATEMENT,
returns,
loc: locStub
}
}

View File

@@ -10,15 +10,21 @@ import {
CallExpression,
ArrayExpression,
ObjectExpression,
SourceLocation,
Position,
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode,
FunctionExpression,
SequenceExpression,
ConditionalExpression,
CacheExpression
CacheExpression,
locStub,
SSRCodegenNode,
TemplateLiteral,
IfStatement,
AssignmentExpression,
ReturnStatement,
VNodeCall,
SequenceExpression
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@@ -31,17 +37,25 @@ import {
import { isString, isArray, isSymbol } from '@vue/shared'
import {
helperNameMap,
TO_STRING,
TO_DISPLAY_STRING,
CREATE_VNODE,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
SET_BLOCK_TRACKING,
CREATE_COMMENT,
CREATE_TEXT
CREATE_TEXT,
PUSH_SCOPE_ID,
POP_SCOPE_ID,
WITH_SCOPE_ID,
WITH_DIRECTIVES,
CREATE_BLOCK,
OPEN_BLOCK,
CREATE_STATIC,
WITH_CTX
} from './runtimeHelpers'
import { ImportItem } from './transform'
type CodegenNode = TemplateChildNode | JSChildNode
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
export interface CodegenResult {
code: string
@@ -58,8 +72,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
indentLevel: number
map?: SourceMapGenerator
helper(key: symbol): string
push(code: string, node?: CodegenNode, openOnly?: boolean): void
resetMapping(loc: SourceLocation): void
push(code: string, node?: CodegenNode): void
indent(): void
deindent(withoutNewLine?: boolean): void
newline(): void
@@ -71,7 +84,12 @@ function createCodegenContext(
mode = 'function',
prefixIdentifiers = mode === 'module',
sourceMap = false,
filename = `template.vue.html`
filename = `template.vue.html`,
scopeId = null,
optimizeBindings = false,
runtimeGlobalName = `Vue`,
runtimeModuleName = `vue`,
ssr = false
}: CodegenOptions
): CodegenContext {
const context: CodegenContext = {
@@ -79,24 +97,22 @@ function createCodegenContext(
prefixIdentifiers,
sourceMap,
filename,
scopeId,
optimizeBindings,
runtimeGlobalName,
runtimeModuleName,
ssr,
source: ast.loc.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indentLevel: 0,
// lazy require source-map implementation, only in non-browser builds!
map:
__BROWSER__ || !sourceMap
? undefined
: new (loadDep('source-map')).SourceMapGenerator(),
map: undefined,
helper(key) {
const name = helperNameMap[key]
return prefixIdentifiers ? name : `_${name}`
return `_${helperNameMap[key]}`
},
push(code, node, openOnly) {
push(code, node) {
context.code += code
if (!__BROWSER__ && context.map) {
if (node) {
@@ -110,16 +126,11 @@ function createCodegenContext(
addMapping(node.loc.start, name)
}
advancePositionWithMutation(context, code)
if (node && !openOnly) {
if (node && node.loc !== locStub) {
addMapping(node.loc.end)
}
}
},
resetMapping(loc: SourceLocation) {
if (!__BROWSER__ && context.map) {
addMapping(loc.start)
}
},
indent() {
newline(++context.indentLevel)
},
@@ -154,9 +165,12 @@ function createCodegenContext(
})
}
if (!__BROWSER__ && context.map) {
context.map.setSourceContent(filename, context.source)
if (!__BROWSER__ && sourceMap) {
// lazy require source-map implementation, only in non-browser builds
context.map = new (loadDep('source-map')).SourceMapGenerator()
context.map!.setSourceContent(filename, context.source)
}
return context
}
@@ -168,63 +182,37 @@ export function generate(
const {
mode,
push,
helper,
prefixIdentifiers,
indent,
deindent,
newline
newline,
scopeId,
ssr
} = context
const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
// preambles
if (mode === 'function') {
// Generate const declaration for helpers
// In prefix mode, we place the const declaration at top so it's done
// only once; But if we not prefixing, we place the declaration inside the
// with block so it doesn't incur the `in` check cost for every helper access.
if (hasHelpers) {
if (prefixIdentifiers) {
push(`const { ${ast.helpers.map(helper).join(', ')} } = Vue\n`)
} else {
// "with" mode.
// save Vue in a separate variable to avoid collision
push(`const _Vue = Vue\n`)
// in "with" mode, helpers are declared inside the with block to avoid
// has check cost, but hoists are lifted out of the function - we need
// to provide the helper here.
if (ast.hoists.length) {
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
.filter(helper => ast.helpers.includes(helper))
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
.join(', ')
push(`const { ${staticHelpers} } = Vue\n`)
}
}
}
genHoists(ast.hoists, context)
newline()
push(`return `)
if (!__BROWSER__ && mode === 'module') {
genModulePreamble(ast, context, genScopeId)
} else {
// generate import statements for helpers
if (hasHelpers) {
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
}
if (ast.imports.length) {
genImports(ast.imports, context)
newline()
}
genHoists(ast.hoists, context)
newline()
push(`export default `)
genFunctionPreamble(ast, context)
}
// enter render function
push(`function render() {`)
if (genScopeId && !ssr) {
push(`const render = _withId(`)
}
if (!ssr) {
push(`function render(_ctx, _cache) {`)
} else {
push(`function ssrRender(_ctx, _push, _parent) {`)
}
indent()
if (useWithBlock) {
push(`with (this) {`)
push(`with (_ctx) {`)
indent()
// function mode const declarations should be inside with block
// also they should be renamed to avoid collision with user properties
@@ -234,35 +222,39 @@ export function generate(
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
.join(', ')} } = _Vue`
)
newline()
if (ast.cached > 0) {
push(`const _cache = $cache`)
newline()
}
push(`\n`)
newline()
}
} else {
push(`const _ctx = this`)
if (ast.cached > 0) {
newline()
push(`const _cache = _ctx.$cache`)
}
newline()
}
// generate asset resolution statements
if (ast.components.length) {
genAssets(ast.components, 'component', context)
if (ast.directives.length || ast.temps > 0) {
newline()
}
}
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context)
if (ast.temps > 0) {
newline()
}
}
if (ast.components.length || ast.directives.length) {
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
push(`\n`)
newline()
}
// generate the VNode tree expression
push(`return `)
if (!ssr) {
push(`return `)
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
@@ -276,27 +268,164 @@ export function generate(
deindent()
push(`}`)
if (genScopeId && !ssr) {
push(`)`)
}
return {
ast,
code: context.code,
map: context.map ? context.map.toJSON() : undefined
// SourceMapGenerator does have toJSON() method but it's not in the types
map: context.map ? (context.map as any).toJSON() : undefined
}
}
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
const {
ssr,
prefixIdentifiers,
push,
newline,
runtimeModuleName,
runtimeGlobalName
} = context
const VueBinding =
!__BROWSER__ && ssr
? `require(${JSON.stringify(runtimeModuleName)})`
: runtimeGlobalName
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
// Generate const declaration for helpers
// In prefix mode, we place the const declaration at top so it's done
// only once; But if we not prefixing, we place the declaration inside the
// with block so it doesn't incur the `in` check cost for every helper access.
if (ast.helpers.length > 0) {
if (!__BROWSER__ && prefixIdentifiers) {
push(
`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
)
} else {
// "with" mode.
// save Vue in a separate variable to avoid collision
push(`const _Vue = ${VueBinding}\n`)
// in "with" mode, helpers are declared inside the with block to avoid
// has check cost, but hoists are lifted out of the function - we need
// to provide the helper here.
if (ast.hoists.length) {
const staticHelpers = [
CREATE_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
CREATE_STATIC
]
.filter(helper => ast.helpers.includes(helper))
.map(aliasHelper)
.join(', ')
push(`const { ${staticHelpers} } = _Vue\n`)
}
}
}
// generate variables for ssr helpers
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
// ssr guaruntees prefixIdentifier: true
push(
`const { ${ast.ssrHelpers
.map(aliasHelper)
.join(', ')} } = require("@vue/server-renderer")\n`
)
}
genHoists(ast.hoists, context)
newline()
push(`return `)
}
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean
) {
const {
push,
helper,
newline,
scopeId,
optimizeBindings,
runtimeModuleName
} = context
if (genScopeId) {
ast.helpers.push(WITH_SCOPE_ID)
if (ast.hoists.length) {
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
}
}
// generate import statements for helpers
if (ast.helpers.length) {
if (optimizeBindings) {
// when bundled with webpack with code-split, calling an import binding
// as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
// incurring both payload size increase and potential perf overhead.
// therefore we assign the imports to vairables (which is a constant ~50b
// cost per-component instead of scaling with template size)
push(
`import { ${ast.helpers
.map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
)
push(
`\n// Binding optimization for webpack code-split\nconst ${ast.helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
.join(', ')}\n`
)
} else {
push(
`import { ${ast.helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
)
}
}
if (ast.ssrHelpers && ast.ssrHelpers.length) {
push(
`import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from "@vue/server-renderer"\n`
)
}
if (ast.imports.length) {
genImports(ast.imports, context)
newline()
}
if (genScopeId) {
push(`const _withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
newline()
}
genHoists(ast.hoists, context)
newline()
push(`export `)
}
function genAssets(
assets: string[],
type: 'component' | 'directive',
context: CodegenContext
{ helper, push, newline }: CodegenContext
) {
const resolver = context.helper(
const resolver = helper(
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
)
for (let i = 0; i < assets.length; i++) {
const id = assets[i]
context.push(
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
)
context.newline()
if (i < assets.length - 1) {
newline()
}
}
}
@@ -304,12 +433,27 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
if (!hoists.length) {
return
}
context.newline()
const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
newline()
// push scope Id before initilaizing hoisted vnodes so that these vnodes
// get the proper scopeId as well.
if (genScopeId) {
push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
newline()
}
hoists.forEach((exp, i) => {
context.push(`const _hoisted_${i + 1} = `)
push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
context.newline()
newline()
})
if (genScopeId) {
push(`${helper(POP_SCOPE_ID)}()`)
newline()
}
}
function genImports(importsOptions: ImportItem[], context: CodegenContext) {
@@ -351,7 +495,8 @@ function genNodeListAsArray(
function genNodeList(
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext,
multilines: boolean = false
multilines: boolean = false,
comma: boolean = true
) {
const { push, newline } = context
for (let i = 0; i < nodes.length; i++) {
@@ -365,10 +510,10 @@ function genNodeList(
}
if (i < nodes.length - 1) {
if (multilines) {
push(',')
comma && push(',')
newline()
} else {
push(', ')
comma && push(', ')
}
}
}
@@ -413,6 +558,10 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.COMMENT:
genComment(node, context)
break
case NodeTypes.VNODE_CALL:
genVNodeCall(node, context)
break
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, context)
break
@@ -425,16 +574,37 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.JS_FUNCTION_EXPRESSION:
genFunctionExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
genSequenceExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
case NodeTypes.JS_CACHE_EXPRESSION:
genCacheExpression(node, context)
break
// SSR only types
case NodeTypes.JS_BLOCK_STATEMENT:
!__BROWSER__ && genNodeList(node.body, context, true, false)
break
case NodeTypes.JS_TEMPLATE_LITERAL:
!__BROWSER__ && genTemplateLiteral(node, context)
break
case NodeTypes.JS_IF_STATEMENT:
!__BROWSER__ && genIfStatement(node, context)
break
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
!__BROWSER__ && genAssignmentExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
!__BROWSER__ && genSequenceExpression(node, context)
break
case NodeTypes.JS_RETURN_STATEMENT:
!__BROWSER__ && genReturnStatement(node, context)
break
/* istanbul ignore next */
case NodeTypes.IF_BRANCH:
// noop
break
default:
if (__DEV__) {
assert(false, `unhandled codegen node type: ${(node as any).type}`)
@@ -459,7 +629,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
const { push, helper } = context
push(`${helper(TO_STRING)}(`)
push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context)
push(`)`)
}
@@ -505,18 +675,60 @@ function genComment(node: CommentNode, context: CodegenContext) {
}
}
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
const { push, helper } = context
const {
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
isForBlock
} = node
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
}
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `)
}
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
context
)
push(`)`)
if (isBlock) {
push(`)`)
}
if (directives) {
push(`, `)
genNode(directives, context)
push(`)`)
}
}
function genNullableArgs(args: any[]): CallExpression['arguments'] {
let i = args.length
while (i--) {
if (args[i] != null) break
}
return args.slice(0, i + 1).map(arg => arg || `null`)
}
// JavaScript
function genCallExpression(node: CallExpression, context: CodegenContext) {
const callee = isString(node.callee)
? node.callee
: context.helper(node.callee)
context.push(callee + `(`, node, true)
context.push(callee + `(`, node)
genNodeList(node.arguments, context)
context.push(`)`)
}
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline, resetMapping } = context
const { push, indent, deindent, newline } = context
const { properties } = node
if (!properties.length) {
push(`{}`, node)
@@ -529,8 +741,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
push(multilines ? `{` : `{ `)
multilines && indent()
for (let i = 0; i < properties.length; i++) {
const { key, value, loc } = properties[i]
resetMapping(loc) // reset source mapping for every property.
const { key, value } = properties[i]
// key
genExpressionAsPropertyKey(key, context)
push(`: `)
@@ -554,8 +765,17 @@ function genFunctionExpression(
node: FunctionExpression,
context: CodegenContext
) {
const { push, indent, deindent } = context
const { params, returns, newline } = node
const { push, indent, deindent, scopeId, mode } = context
const { params, returns, body, newline, isSlot } = node
// slot functions also need to push scopeId before rendering its content
const genScopeId =
!__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
if (genScopeId) {
push(`_withId(`)
} else if (isSlot) {
push(`_${helperNameMap[WITH_CTX]}(`)
}
push(`(`, node)
if (isArray(params)) {
genNodeList(params, context)
@@ -563,27 +783,36 @@ function genFunctionExpression(
genNode(params, context)
}
push(`) => `)
if (newline) {
if (newline || body) {
push(`{`)
indent()
push(`return `)
}
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
if (returns) {
if (newline) {
push(`return `)
}
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
} else if (body) {
genNode(body, context)
}
if (newline) {
if (newline || body) {
deindent()
push(`}`)
}
if (genScopeId || isSlot) {
push(`)`)
}
}
function genConditionalExpression(
node: ConditionalExpression,
context: CodegenContext
) {
const { test, consequent, alternate } = node
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsParens = !isSimpleIdentifier(test.content)
@@ -592,15 +821,17 @@ function genConditionalExpression(
needsParens && push(`)`)
} else {
push(`(`)
genCompoundExpression(test, context)
genNode(test, context)
push(`)`)
}
indent()
needNewline && indent()
context.indentLevel++
needNewline || push(` `)
push(`? `)
genNode(consequent, context)
context.indentLevel--
newline()
needNewline && newline()
needNewline || push(` `)
push(`: `)
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
if (!isNested) {
@@ -610,16 +841,7 @@ function genConditionalExpression(
if (!isNested) {
context.indentLevel--
}
deindent(true /* without newline */)
}
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
needNewline && deindent(true /* without newline */)
}
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
@@ -642,3 +864,77 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
}
push(`)`)
}
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
const { push, indent, deindent } = context
push('`')
const l = node.elements.length
const multilines = l > 3
for (let i = 0; i < l; i++) {
const e = node.elements[i]
if (isString(e)) {
push(e.replace(/`/g, '\\`'))
} else {
push('${')
if (multilines) indent()
genNode(e, context)
if (multilines) deindent()
push('}')
}
}
push('`')
}
function genIfStatement(node: IfStatement, context: CodegenContext) {
const { push, indent, deindent } = context
const { test, consequent, alternate } = node
push(`if (`)
genNode(test, context)
push(`) {`)
indent()
genNode(consequent, context)
deindent()
push(`}`)
if (alternate) {
push(` else `)
if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
genIfStatement(alternate, context)
} else {
push(`{`)
indent()
genNode(alternate, context)
deindent()
push(`}`)
}
}
}
function genAssignmentExpression(
node: AssignmentExpression,
context: CodegenContext
) {
genNode(node.left, context)
context.push(` = `)
genNode(node.right, context)
}
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
}
function genReturnStatement(
{ returns }: ReturnStatement,
context: CodegenContext
) {
context.push(`return `)
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
}

View File

@@ -0,0 +1,100 @@
import { CompilerOptions } from './options'
import { baseParse } from './parse'
import { transform, NodeTransform, DirectiveTransform } from './transform'
import { generate, CodegenResult } from './codegen'
import { RootNode } from './ast'
import { isString } from '@vue/shared'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression'
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
import { transformElement } from './transforms/transformElement'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
export type TransformPreset = [
NodeTransform[],
Record<string, DirectiveTransform>
]
export function getBaseTransformPreset(
prefixIdentifiers?: boolean
): TransformPreset {
return [
[
transformOnce,
transformIf,
transformFor,
...(!__BROWSER__ && prefixIdentifiers
? [
// order is important
trackVForSlotScopes,
transformExpression
]
: []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText
],
{
on: transformOn,
bind: transformBind,
model: transformModel
}
]
}
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
/* istanbul ignore if */
if (__BROWSER__) {
if (options.prefixIdentifiers === true) {
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
} else if (isModuleMode) {
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
}
const prefixIdentifiers =
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
if (!prefixIdentifiers && options.cacheHandlers) {
onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
}
if (options.scopeId && !isModuleMode) {
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
}
const ast = isString(template) ? baseParse(template, options) : template
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
prefixIdentifiers
)
transform(ast, {
...options,
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: {
...directiveTransforms,
...(options.directiveTransforms || {}) // user transforms
}
})
return generate(ast, {
...options,
prefixIdentifiers
})
}

View File

@@ -16,11 +16,14 @@ export function defaultOnError(error: CompilerError) {
export function createCompilerError<T extends number>(
code: T,
loc?: SourceLocation,
messages?: { [code: number]: string }
messages?: { [code: number]: string },
additionalMessage?: string
): T extends ErrorCodes ? CoreCompilerError : CompilerError {
const msg = __DEV__ || !__BROWSER__ ? (messages || errorMessages)[code] : code
const locInfo = loc ? ` (${loc.start.line}:${loc.start.column})` : ``
const error = new SyntaxError(msg + locInfo) as CompilerError
const msg =
__DEV__ || !__BROWSER__
? (messages || errorMessages)[code] + (additionalMessage || ``)
: code
const error = new SyntaxError(String(msg)) as CompilerError
error.code = code
error.loc = loc
return error as any
@@ -58,7 +61,6 @@ export const enum ErrorCodes {
UNEXPECTED_NULL_CHARACTER,
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
UNEXPECTED_SOLIDUS_IN_TAG,
UNKNOWN_NAMED_CHARACTER_REFERENCE,
// Vue-specific parse errors
X_INVALID_END_TAG,
@@ -74,19 +76,21 @@ export const enum ErrorCodes {
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
X_V_SLOT_MIXED_SLOT_USAGE,
X_V_SLOT_DUPLICATE_SLOT_NAMES,
X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
X_V_SLOT_MISPLACED,
X_V_MODEL_NO_EXPRESSION,
X_V_MODEL_MALFORMED_EXPRESSION,
X_V_MODEL_ON_SCOPE_VARIABLE,
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
// generic errors
X_PREFIX_ID_NOT_SUPPORTED,
X_MODULE_MODE_NOT_SUPPORTED,
X_CACHE_HANDLER_NOT_SUPPORTED,
X_SCOPE_ID_NOT_SUPPORTED,
// Special value for higher-order compilers to pick up the last code
// to avoid collision of error codes. This should always be kept as the last
@@ -140,11 +144,10 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
"'<?' is allowed only in XML context.",
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
[ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE]: 'Unknown entity name.',
// Vue-specific parse errors
[ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
[ErrorCodes.X_MISSING_END_TAG]: 'End tag was not found.',
[ErrorCodes.X_MISSING_END_TAG]: 'Element is missing end tag.',
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
'Interpolation end sign was not found.',
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
@@ -159,24 +162,24 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
[ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT]:
`Named v-slot on component. ` +
`Named slots should use <template v-slot> syntax nested inside the component.`,
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
`Mixed v-slot usage on both the component and nested <template>.` +
`The default slot should also use <template> syntax when there are other ` +
`named slots to avoid scope ambiguity.`,
`When there are multiple named slots, all slots should use <template> ` +
`syntax to avoid scope ambiguity.`,
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
[ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN]:
`Extraneous children found when component has explicit slots. ` +
`These children will be ignored.`,
[ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
`Extraneous children found when component already has explicitly named ` +
`default slot. These children will be ignored.`,
[ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or <template> tags.`,
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
[ErrorCodes.X_INVALID_EXPRESSION]: `Invalid JavaScript expression.`,
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
// generic errors
[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.`
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
[ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`
}

View File

@@ -1,90 +1,20 @@
import { CompilerOptions } from './options'
import { parse } from './parse'
import { transform } from './transform'
import { generate, CodegenResult } from './codegen'
import { RootNode } from './ast'
import { isString } from '@vue/shared'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression'
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
import { transformElement } from './transforms/transformElement'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
// we name it `baseCompile` so that higher order compilers like @vue/compiler-dom
// can export `compile` while re-exporting everything else.
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
/* istanbul ignore if */
if (__BROWSER__) {
const onError = options.onError || defaultOnError
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 prefixIdentifiers =
!__BROWSER__ &&
(options.prefixIdentifiers === true || options.mode === 'module')
transform(ast, {
...options,
prefixIdentifiers,
nodeTransforms: [
transformOnce,
transformIf,
transformFor,
...(prefixIdentifiers
? [
// order is important
trackVForSlotScopes,
transformExpression
]
: []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: {
on: transformOn,
bind: transformBind,
model: transformModel,
...(options.directiveTransforms || {}) // user transforms
}
})
return generate(ast, {
...options,
prefixIdentifiers
})
}
export { baseCompile } from './compile'
// Also expose lower level APIs & types
export {
CompilerOptions,
ParserOptions,
TransformOptions,
CodegenOptions
CodegenOptions,
HoistTransform
} from './options'
export { parse, TextModes } from './parse'
export { baseParse, TextModes } from './parse'
export {
transform,
createStructuralDirectiveTransform,
TransformContext,
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
NodeTransform,
StructuralDirectiveTransform,
DirectiveTransform
@@ -96,19 +26,32 @@ export {
CompilerError,
createCompilerError
} from './errors'
export * from './ast'
export * from './utils'
export { registerRuntimeHelpers } from './runtimeHelpers'
export * from './runtimeHelpers'
// expose transforms so higher-order compilers can import and extend them
export { getBaseTransformPreset, TransformPreset } from './compile'
export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn'
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
import { generateCodeFrame as _genCodeFrame } from '@vue/shared'
const generateCodeFrame = _genCodeFrame as (
source: string,
start?: number,
end?: number
) => string
export { generateCodeFrame }
export { transformBind } from './transforms/vBind'
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
export { processIf } from './transforms/vIf'
export { processFor, createForLoopParams } from './transforms/vFor'
export {
transformExpression,
processExpression
} from './transforms/transformExpression'
export {
buildSlots,
SlotFnBuilder,
trackVForSlotScopes,
trackSlotScopes
} from './transforms/vSlot'
export {
transformElement,
resolveComponentType,
buildProps
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { generateCodeFrame } from '@vue/shared'

View File

@@ -1,16 +1,30 @@
import { ElementNode, Namespace } from './ast'
import { ElementNode, Namespace, JSChildNode, PlainElementNode } from './ast'
import { TextModes } from './parse'
import { CompilerError } from './errors'
import { NodeTransform, DirectiveTransform } from './transform'
import {
NodeTransform,
DirectiveTransform,
TransformContext
} from './transform'
import { ParserPlugin } from '@babel/parser'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex
isPreTag?: (tag: string) => boolean // e.g. <pre> where whitespace is intact
isCustomElement?: (tag: string) => boolean
// e.g. platform native elements, e.g. <div> for browsers
isNativeTag?: (tag: string) => boolean
// e.g. native elements that can self-close, e.g. <img>, <br>, <hr>
isVoidTag?: (tag: string) => boolean
// e.g. elements that should preserve whitespace inside, e.g. <pre>
isPreTag?: (tag: string) => boolean
// platform-specific built-in components e.g. <Transition>
isBuiltInComponent?: (tag: string) => symbol | void
// separate option for end users to extend the native elements list
isCustomElement?: (tag: string) => boolean
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
getTextMode?: (tag: string, ns: Namespace) => TextModes
getTextMode?: (
tag: string,
ns: Namespace,
parent: ElementNode | undefined
) => TextModes
delimiters?: [string, string] // ['{{', '}}']
// Map to HTML entities. E.g., `{ "amp;": "&" }`
@@ -19,27 +33,46 @@ export interface ParserOptions {
// this number is based on the map above, but it should be pre-computed
// to avoid the cost on every parse() call.
maxCRNameLength?: number
onError?: (error: CompilerError) => void
}
export type HoistTransform = (
node: PlainElementNode,
context: TransformContext
) => JSChildNode
export interface TransformOptions {
nodeTransforms?: NodeTransform[]
directiveTransforms?: { [name: string]: DirectiveTransform }
directiveTransforms?: Record<string, DirectiveTransform | undefined>
// an optional hook to transform a node being hoisted.
// used by compiler-dom to turn hoisted nodes into stringified HTML vnodes.
transformHoist?: HoistTransform | null
isBuiltInComponent?: (tag: string) => symbol | void
// Transform expressions like {{ foo }} to `_ctx.foo`.
// Default: mode === 'module'
// If this option is false, the generated code will be wrapped in a
// `with (this) { ... }` block.
// - This is force-enabled in module mode, since modules are by default strict
// and cannot use `with`
// - Default: mode === 'module'
prefixIdentifiers?: boolean
// Hoist static VNodes and props objects to `_hoisted_x` constants
// Default: false
// - Default: false
hoistStatic?: boolean
// Cache v-on handlers to avoid creating new inline functions on each render,
// also avoids the need for dynamically patching the handlers by wrapping it.
// e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
// option it's compiled to:
// `{ onClick: _cache[0] || (_cache[0] = e => _ctx.foo(e)) }`
// Default: false
// - Requires "prefixIdentifiers" to be enabled because it relies on scope
// analysis to determine if a handler is safe to cache.
// - Default: false
cacheHandlers?: boolean
// a list of parser plugins to enable for @babel/parser
// https://babeljs.io/docs/en/next/babel-parser#plugins
expressionPlugins?: ParserPlugin[]
// SFC scoped styles ID
scopeId?: string | null
ssr?: boolean
onError?: (error: CompilerError) => void
}
@@ -49,19 +82,26 @@ export interface CodegenOptions {
// - Function mode will generate a single `const { helpers... } = Vue`
// statement and return the render function. It is meant to be used with
// `new Function(code)()` to generate a render function at runtime.
// Default: 'function'
// - Default: 'function'
mode?: 'module' | 'function'
// Prefix suitable identifiers with _ctx.
// If this option is false, the generated code will be wrapped in a
// `with (this) { ... }` block.
// Default: false
prefixIdentifiers?: boolean
// Generate source map?
// Default: false
// - Default: false
sourceMap?: boolean
// Filename for source map generation.
// Default: `template.vue.html`
// - Default: `template.vue.html`
filename?: string
// SFC scoped styles ID
scopeId?: string | null
// we need to know about this to generate proper preambles
prefixIdentifiers?: boolean
// option to optimize helper import bindings via variable assignment
// (only used for webpack code-split)
optimizeBindings?: boolean
// for specifying where to import helpers
runtimeModuleName?: string
runtimeGlobalName?: string
// generate ssr-specific code?
ssr?: boolean
}
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions

View File

@@ -1,5 +1,5 @@
import { ParserOptions } from './options'
import { NO, isArray } from '@vue/shared'
import { NO, isArray, makeMap } from '@vue/shared'
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import {
assert,
@@ -21,11 +21,11 @@ import {
SourceLocation,
TextNode,
TemplateChildNode,
InterpolationNode
InterpolationNode,
createRoot
} from './ast'
import { extend } from '@vue/shared'
// `isNativeTag` is optional, others are required
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
Pick<ParserOptions, OptionalOptions>
@@ -64,25 +64,20 @@ interface ParserContext {
offset: number
line: number
column: number
inPre: boolean
inPre: boolean // HTML <pre> tag, preserve whitespaces
inVPre: boolean // v-pre, do not process directives and interpolations
}
export function parse(content: string, options: ParserOptions = {}): RootNode {
export function baseParse(
content: string,
options: ParserOptions = {}
): RootNode {
const context = createParserContext(content, options)
const start = getCursor(context)
return {
type: NodeTypes.ROOT,
children: parseChildren(context, TextModes.DATA, []),
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
codegenNode: undefined,
loc: getSelection(context, start)
}
return createRoot(
parseChildren(context, TextModes.DATA, []),
getSelection(context, start)
)
}
function createParserContext(
@@ -99,7 +94,8 @@ function createParserContext(
offset: 0,
originalSource: content,
source: content,
inPre: false
inPre: false,
inVPre: false
}
}
@@ -117,11 +113,11 @@ function parseChildren(
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (mode === TextModes.DATA) {
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
// '{{'
node = parseInterpolation(context, mode)
} else if (s[0] === '<') {
} else if (mode === TextModes.DATA && s[0] === '<') {
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
if (s.length === 1) {
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
@@ -193,45 +189,51 @@ function parseChildren(
// Whitespace management for more efficient output
// (same as v2 whitespace: 'condense')
let removedWhitespace = false
if (
mode !== TextModes.RAWTEXT &&
(!parent || !context.options.isPreTag(parent.tag))
) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === NodeTypes.TEXT) {
if (!node.content.trim()) {
const prev = nodes[i - 1]
const next = nodes[i + 1]
// If:
// - the whitespace is the first or last node, or:
// - the whitespace is adjacent to a comment, or:
// - the whitespace is between two elements AND contains newline
// Then the whitespace is ignored.
if (
!prev ||
!next ||
prev.type === NodeTypes.COMMENT ||
next.type === NodeTypes.COMMENT ||
(prev.type === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT &&
/[\r\n]/.test(node.content))
) {
removedWhitespace = true
nodes[i] = null as any
if (mode !== TextModes.RAWTEXT) {
if (!context.inPre) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === NodeTypes.TEXT) {
if (!node.content.trim()) {
const prev = nodes[i - 1]
const next = nodes[i + 1]
// If:
// - the whitespace is the first or last node, or:
// - the whitespace is adjacent to a comment, or:
// - the whitespace is between two elements AND contains newline
// Then the whitespace is ignored.
if (
!prev ||
!next ||
prev.type === NodeTypes.COMMENT ||
next.type === NodeTypes.COMMENT ||
(prev.type === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT &&
/[\r\n]/.test(node.content))
) {
removedWhitespace = true
nodes[i] = null as any
} else {
// Otherwise, condensed consecutive whitespace inside the text down to
// a single space
node.content = ' '
}
} else {
// Otherwise, condensed consecutive whitespace inside the text down to
// a single space
node.content = ' '
node.content = node.content.replace(/\s+/g, ' ')
}
} else {
node.content = node.content.replace(/\s+/g, ' ')
}
}
} else {
// remove leading newline per html spec
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = nodes[0]
if (first && first.type === NodeTypes.TEXT) {
first.content = first.content.replace(/^\r?\n/, '')
}
}
}
return removedWhitespace ? nodes.filter(node => node !== null) : nodes
return removedWhitespace ? nodes.filter(Boolean) : nodes
}
function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {
@@ -353,9 +355,11 @@ function parseElement(
// Start tag.
const wasInPre = context.inPre
const wasInVPre = context.inVPre
const parent = last(ancestors)
const element = parseTag(context, TagType.Start, parent)
const isPreBoundary = context.inPre && !wasInPre
const isVPreBoundary = context.inVPre && !wasInVPre
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element
@@ -363,7 +367,7 @@ function parseElement(
// Children.
ancestors.push(element)
const mode = context.options.getTextMode(element.tag, element.ns)
const mode = context.options.getTextMode(element.tag, element.ns, parent)
const children = parseChildren(context, mode, ancestors)
ancestors.pop()
@@ -373,7 +377,7 @@ function parseElement(
if (startsWithEndTagOpen(context.source, element.tag)) {
parseTag(context, TagType.End, parent)
} else {
emitError(context, ErrorCodes.X_MISSING_END_TAG)
emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
const first = children[0]
if (first && startsWith(first.loc.source, '<!--')) {
@@ -387,6 +391,9 @@ function parseElement(
if (isPreBoundary) {
context.inPre = false
}
if (isVPreBoundary) {
context.inVPre = false
}
return element
}
@@ -395,6 +402,10 @@ const enum TagType {
End
}
const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
`if,else,else-if,for,slot`
)
/**
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
*/
@@ -425,12 +436,17 @@ function parseTag(
// Attributes.
let props = parseAttributes(context, type)
// check <pre> tag
if (context.options.isPreTag(tag)) {
context.inPre = true
}
// check v-pre
if (
!context.inPre &&
!context.inVPre &&
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
) {
context.inPre = true
context.inVPre = true
// reset context
extend(context, cursor)
context.source = currentSource
@@ -452,20 +468,32 @@ function parseTag(
let tagType = ElementTypes.ELEMENT
const options = context.options
if (!context.inPre && !options.isCustomElement(tag)) {
if (options.isNativeTag) {
if (!context.inVPre && !options.isCustomElement(tag)) {
const hasVIs = props.some(
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
)
if (options.isNativeTag && !hasVIs) {
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
} else if (
hasVIs ||
isCoreComponent(tag) ||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
/^[A-Z]/.test(tag)
/^[A-Z]/.test(tag) ||
tag === 'component'
) {
tagType = ElementTypes.COMPONENT
}
if (tag === 'slot') {
tagType = ElementTypes.SLOT
} else if (tag === 'template') {
} else if (
tag === 'template' &&
props.some(p => {
return (
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
)
})
) {
tagType = ElementTypes.TEMPLATE
}
}
@@ -539,7 +567,7 @@ function parseAttribute(
{
const pattern = /["'<]/g
let m: RegExpExecArray | null
while ((m = pattern.exec(name)) !== null) {
while ((m = pattern.exec(name))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
@@ -570,7 +598,7 @@ function parseAttribute(
}
const loc = getSelection(context, start)
if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
if (!context.inVPre && /^(v-|:|@|#)/.test(name)) {
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
name
)!
@@ -688,9 +716,9 @@ function parseAttributeValue(
if (!match) {
return undefined
}
let unexpectedChars = /["'<=`]/g
const unexpectedChars = /["'<=`]/g
let m: RegExpExecArray | null
while ((m = unexpectedChars.exec(match[0])) !== null) {
while ((m = unexpectedChars.exec(match[0]))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
@@ -818,8 +846,8 @@ function parseTextData(
if (head[0] === '&') {
// Named character reference.
let name = '',
value: string | undefined = undefined
let name = ''
let value: string | undefined = undefined
if (/[0-9a-z]/i.test(rawText[1])) {
for (
let length = context.options.maxCRNameLength;
@@ -834,7 +862,7 @@ function parseTextData(
if (
mode === TextModes.ATTRIBUTE_VALUE &&
!semi &&
/[=a-z0-9]/i.test(rawText[1 + name.length] || '')
/[=a-z0-9]/i.test(rawText[name.length + 1] || '')
) {
decodedText += '&' + name
advance(1 + name.length)
@@ -849,7 +877,6 @@ function parseTextData(
}
}
} else {
emitError(context, ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE)
decodedText += '&' + name
advance(1 + name.length)
}
@@ -964,9 +991,9 @@ function getNewPosition(
function emitError(
context: ParserContext,
code: ErrorCodes,
offset?: number
offset?: number,
loc: Position = getCursor(context)
): void {
const loc = getCursor(context)
if (offset) {
loc.offset += offset
loc.column += offset

View File

@@ -1,5 +1,5 @@
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
export const PORTAL = Symbol(__DEV__ ? `Portal` : ``)
export const TELEPORT = Symbol(__DEV__ ? `Teleport` : ``)
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
@@ -8,6 +8,7 @@ export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
__DEV__ ? `resolveDynamicComponent` : ``
@@ -17,18 +18,22 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
export const TO_STRING = Symbol(__DEV__ ? `toString` : ``)
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
// Name mapping for runtime helpers that need to be imported from 'vue' in
// generated code. Make sure these are correctly exported in the runtime!
// Using `any` here because TS doesn't allow symbols as index type.
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`,
[PORTAL]: `Portal`,
[TELEPORT]: `Teleport`,
[SUSPENSE]: `Suspense`,
[KEEP_ALIVE]: `KeepAlive`,
[BASE_TRANSITION]: `BaseTransition`,
@@ -37,6 +42,7 @@ export const helperNameMap: any = {
[CREATE_VNODE]: `createVNode`,
[CREATE_COMMENT]: `createCommentVNode`,
[CREATE_TEXT]: `createTextVNode`,
[CREATE_STATIC]: `createStaticVNode`,
[RESOLVE_COMPONENT]: `resolveComponent`,
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
@@ -44,11 +50,15 @@ export const helperNameMap: any = {
[RENDER_LIST]: `renderList`,
[RENDER_SLOT]: `renderSlot`,
[CREATE_SLOTS]: `createSlots`,
[TO_STRING]: `toString`,
[TO_DISPLAY_STRING]: `toDisplayString`,
[MERGE_PROPS]: `mergeProps`,
[TO_HANDLERS]: `toHandlers`,
[CAMELIZE]: `camelize`,
[SET_BLOCK_TRACKING]: `setBlockTracking`
[SET_BLOCK_TRACKING]: `setBlockTracking`,
[PUSH_SCOPE_ID]: `pushScopeId`,
[POP_SCOPE_ID]: `popScopeId`,
[WITH_SCOPE_ID]: `withScopeId`,
[WITH_CTX]: `withCtx`
}
export function registerRuntimeHelpers(helpers: any) {

View File

@@ -12,11 +12,10 @@ import {
JSChildNode,
SimpleExpressionNode,
ElementTypes,
ElementCodegenNode,
ComponentCodegenNode,
createCallExpression,
CacheExpression,
createCacheExpression
createCacheExpression,
TemplateLiteral,
createVNodeCall
} from './ast'
import {
isString,
@@ -27,14 +26,14 @@ import {
} from '@vue/shared'
import { defaultOnError } from './errors'
import {
TO_STRING,
TO_DISPLAY_STRING,
FRAGMENT,
helperNameMap,
WITH_DIRECTIVES,
CREATE_BLOCK,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK
} from './runtimeHelpers'
import { isVSlot, createBlockExpression } from './utils'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
// There are two types of transforms:
@@ -61,7 +60,8 @@ export type DirectiveTransform = (
export interface DirectiveTransformResult {
props: Property[]
needRuntime: boolean | symbol
needRuntime?: boolean | symbol
ssrTagParts?: TemplateLiteral['elements']
}
// A structural directive transform is a technically a NodeTransform;
@@ -84,6 +84,7 @@ export interface TransformContext extends Required<TransformOptions> {
directives: Set<string>
hoists: JSChildNode[]
imports: Set<ImportItem>
temps: number
cached: number
identifiers: { [name: string]: number | undefined }
scopes: {
@@ -106,7 +107,7 @@ export interface TransformContext extends Required<TransformOptions> {
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
}
function createTransformContext(
export function createTransformContext(
root: RootNode,
{
prefixIdentifiers = false,
@@ -114,17 +115,36 @@ function createTransformContext(
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
transformHoist = null,
isBuiltInComponent = NOOP,
expressionPlugins = [],
scopeId = null,
ssr = false,
onError = defaultOnError
}: TransformOptions
): TransformContext {
const context: TransformContext = {
// options
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
transformHoist,
isBuiltInComponent,
expressionPlugins,
scopeId,
ssr,
onError,
// state
root,
helpers: new Set(),
components: new Set(),
directives: new Set(),
hoists: [],
imports: new Set(),
temps: 0,
cached: 0,
identifiers: {},
scopes: {
@@ -133,25 +153,17 @@ function createTransformContext(
vPre: 0,
vOnce: 0
},
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
isBuiltInComponent,
onError,
parent: null,
currentNode: root,
childIndex: 0,
// methods
helper(name) {
context.helpers.add(name)
return name
},
helperString(name) {
return (
(context.prefixIdentifiers ? `` : `_`) +
helperNameMap[context.helper(name)]
)
return `_${helperNameMap[context.helper(name)]}`
},
replaceNode(node) {
/* istanbul ignore if */
@@ -251,10 +263,20 @@ export function transform(root: RootNode, options: TransformOptions) {
if (options.hoistStatic) {
hoistStatic(root, context)
}
finalizeRoot(root, context)
if (!options.ssr) {
createRootCodegen(root, context)
}
// finalize meta information
root.helpers = [...context.helpers]
root.components = [...context.components]
root.directives = [...context.directives]
root.imports = [...context.imports]
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
}
function finalizeRoot(root: RootNode, context: TransformContext) {
function createRootCodegen(root: RootNode, context: TransformContext) {
const { helper } = context
const { children } = root
const child = children[0]
@@ -263,20 +285,13 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
if (isSingleElementRoot(root, child) && child.codegenNode) {
// single element root is never hoisted so codegenNode will never be
// SimpleExpressionNode
const codegenNode = child.codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| CacheExpression
if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) {
if (codegenNode.callee === WITH_DIRECTIVES) {
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
} else {
codegenNode.callee = helper(CREATE_BLOCK)
}
root.codegenNode = createBlockExpression(codegenNode, context)
} else {
root.codegenNode = codegenNode
const codegenNode = child.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
root.codegenNode = codegenNode
} else {
// - single <slot/>, IfNode, ForNode: already blocks.
// - single text node: always patched.
@@ -285,27 +300,21 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
root.codegenNode = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
`null`,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`,
undefined,
undefined,
true
)
} else {
// no children = noop. codegen will return null.
}
// finalize meta information
root.helpers = [...context.helpers]
root.components = [...context.components]
root.directives = [...context.directives]
root.imports = [...context.imports]
root.hoists = context.hoists
root.cached = context.cached
}
export function traverseChildren(
@@ -319,7 +328,6 @@ export function traverseChildren(
for (; i < parent.children.length; i++) {
const child = parent.children[i]
if (isString(child)) continue
context.currentNode = child
context.parent = parent
context.childIndex = i
context.onNodeRemoved = nodeRemoved
@@ -331,6 +339,7 @@ export function traverseNode(
node: RootNode | TemplateChildNode,
context: TransformContext
) {
context.currentNode = node
// apply transform plugins
const { nodeTransforms } = context
const exitFns = []
@@ -354,21 +363,26 @@ export function traverseNode(
switch (node.type) {
case NodeTypes.COMMENT:
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_COMMENT)
if (!context.ssr) {
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_COMMENT)
}
break
case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper
context.helper(TO_STRING)
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING)
}
break
// for container types, further traverse downwards
case NodeTypes.IF:
for (let i = 0; i < node.branches.length; i++) {
traverseChildren(node.branches[i], context)
traverseNode(node.branches[i], context)
}
break
case NodeTypes.IF_BRANCH:
case NodeTypes.FOR:
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:

View File

@@ -8,11 +8,9 @@ import {
ComponentNode,
TemplateNode,
ElementNode,
PlainElementCodegenNode,
CodegenNodeWithDirective
VNodeCall
} from '../ast'
import { TransformContext } from '../transform'
import { WITH_DIRECTIVES } from '../runtimeHelpers'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet, findProp } from '../utils'
@@ -21,6 +19,8 @@ export function hoistStatic(root: RootNode, context: TransformContext) {
root.children,
context,
new Map(),
// Root node is unfortuantely non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0])
)
}
@@ -52,13 +52,18 @@ function walk(
) {
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
// whole tree is static
child.codegenNode = context.hoist(child.codegenNode!)
;(child.codegenNode as VNodeCall).patchFlag =
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
const hoisted = context.transformHoist
? context.transformHoist(child, context)
: child.codegenNode!
child.codegenNode = context.hoist(hoisted)
continue
} else {
// node may contain dynamic children, but its props may be eligible for
// hoisting.
const codegenNode = child.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type === NodeTypes.VNODE_CALL) {
const flag = getPatchFlag(codegenNode)
if (
(!flag ||
@@ -68,8 +73,8 @@ function walk(
!hasCachedProps(child)
) {
const props = getNodeProps(child)
if (props && props !== `null`) {
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
if (props) {
codegenNode.props = context.hoist(props)
}
}
}
@@ -86,6 +91,11 @@ function walk(
// Do not hoist v-if single child because it has to be a block
walk(branchChildren, context, resultCache, branchChildren.length === 1)
}
} else if (
child.type === NodeTypes.TEXT_CALL &&
isStaticNode(child.content, resultCache)
) {
child.codegenNode = context.hoist(child.codegenNode)
}
}
}
@@ -104,7 +114,7 @@ export function isStaticNode(
return cached
}
const codegenNode = node.codegenNode!
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
return false
}
const flag = getPatchFlag(codegenNode)
@@ -116,6 +126,12 @@ export function isStaticNode(
return false
}
}
// only svg/foreignObject could be block here, however if they are static
// then they don't need to be blocks since there will be no nested
// updates.
if (codegenNode.isBlock) {
codegenNode.isBlock = false
}
resultCache.set(node, true)
return true
} else {
@@ -127,6 +143,7 @@ export function isStaticNode(
return true
case NodeTypes.IF:
case NodeTypes.FOR:
case NodeTypes.IF_BRANCH:
return false
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
@@ -157,14 +174,20 @@ function hasCachedProps(node: PlainElementNode): boolean {
return false
}
const props = getNodeProps(node)
if (
props &&
props !== 'null' &&
props.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
const { properties } = props
for (let i = 0; i < properties.length; i++) {
if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) {
const val = properties[i].value
if (val.type === NodeTypes.JS_CACHE_EXPRESSION) {
return true
}
// merged event handlers
if (
val.type === NodeTypes.JS_ARRAY_EXPRESSION &&
val.elements.some(
e => !isString(e) && e.type === NodeTypes.JS_CACHE_EXPRESSION
)
) {
return true
}
}
@@ -174,30 +197,12 @@ function hasCachedProps(node: PlainElementNode): boolean {
function getNodeProps(node: PlainElementNode) {
const codegenNode = node.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
return getVNodeArgAt(
codegenNode,
1
) as PlainElementCodegenNode['arguments'][1]
if (codegenNode.type === NodeTypes.VNODE_CALL) {
return codegenNode.props
}
}
type NonCachedCodegenNode =
| PlainElementCodegenNode
| CodegenNodeWithDirective<PlainElementCodegenNode>
function getVNodeArgAt(
node: NonCachedCodegenNode,
index: number
): PlainElementCodegenNode['arguments'][number] {
return getVNodeCall(node).arguments[index]
}
function getVNodeCall(node: NonCachedCodegenNode) {
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node
}
function getPatchFlag(node: NonCachedCodegenNode): number | undefined {
const flag = getVNodeArgAt(node, 3) as string
function getPatchFlag(node: VNodeCall): number | undefined {
const flag = node.patchFlag
return flag ? parseInt(flag, 10) : undefined
}

View File

@@ -0,0 +1,3 @@
import { DirectiveTransform } from '../transform'
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })

View File

@@ -13,27 +13,31 @@ import {
createObjectProperty,
createSimpleExpression,
createObjectExpression,
Property
Property,
ComponentNode,
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
createVNodeCall
} from '../ast'
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
import { PatchFlags, PatchFlagNames, isSymbol, isOn } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT,
RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS,
TO_HANDLERS,
PORTAL,
TELEPORT,
KEEP_ALIVE
} from '../runtimeHelpers'
import {
getInnerRange,
isVSlot,
toValidAssetId,
findProp,
isCoreComponent
isCoreComponent,
isBindKey,
findDir
} from '../utils'
import { buildSlots } from './vSlot'
import { isStaticNode } from './hoistStatic'
@@ -45,107 +49,97 @@ const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
// generate a JavaScript AST for this element's codegen
export const transformElement: NodeTransform = (node, context) => {
if (
node.type !== NodeTypes.ELEMENT ||
// handled by transformSlotOutlet
node.tagType === ElementTypes.SLOT ||
// <template v-if/v-for> should have already been replaced
// <template v-slot> is handled by buildSlots
(node.tagType === ElementTypes.TEMPLATE && node.props.some(isVSlot))
!(
node.type === NodeTypes.ELEMENT &&
(node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT)
)
) {
return
}
// perform the work on exit, after all child expressions have been
// processed and merged.
return function postTransformElement() {
const { tag, tagType, props } = node
const builtInComponentSymbol =
isCoreComponent(tag) || context.isBuiltInComponent(tag)
const isComponent = tagType === ElementTypes.COMPONENT
const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let hasProps = props.length > 0
// The goal of the transform is to create a codegenNode implementing the
// VNodeCall interface.
const vnodeTag = isComponent
? resolveComponentType(node as ComponentNode, context)
: `"${tag}"`
let vnodeProps: VNodeCall['props']
let vnodeChildren: VNodeCall['children']
let vnodePatchFlag: VNodeCall['patchFlag']
let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let dynamicComponent: string | CallExpression | undefined
let vnodeDirectives: VNodeCall['directives']
// handle dynamic component
const isProp = findProp(node, 'is')
if (tag === 'component') {
if (isProp) {
// static <component is="foo" />
if (isProp.type === NodeTypes.ATTRIBUTE) {
const tag = isProp.value && isProp.value.content
if (tag) {
context.helper(RESOLVE_COMPONENT)
context.components.add(tag)
dynamicComponent = toValidAssetId(tag, `component`)
}
}
// dynamic <component :is="asdf" />
else if (isProp.exp) {
dynamicComponent = createCallExpression(
context.helper(RESOLVE_DYNAMIC_COMPONENT),
// _ctx.$ exposes the owner instance of current render function
[isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`]
let shouldUseBlock =
!isComponent &&
// <svg> and <foreignObject> must be forced into blocks so that block
// updates inside get proper isSVG flag at runtime. (#639, #643)
// This is technically web-specific, but splitting the logic out of core
// leads to too much unnecessary complexity.
(tag === 'svg' ||
tag === 'foreignObject' ||
// #938: elements with dynamic keys should be forced into blocks
findProp(node, 'key', true))
// props
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
const directives = propsBuildResult.directives
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context))
) as DirectiveArguments)
: undefined
}
// children
if (node.children.length > 0) {
if (vnodeTag === KEEP_ALIVE) {
// Although a built-in component, we compile KeepAlive with raw children
// instead of slot functions so that it can be used inside Transition
// or other Transition-wrapping HOCs.
// To ensure correct updates with block optimizations, we need to:
// 1. Force keep-alive into a block. This avoids its children being
// collected by a parent block.
shouldUseBlock = true
// 2. Force keep-alive to always be updated, since it uses raw children.
patchFlag |= PatchFlags.DYNAMIC_SLOTS
if (__DEV__ && node.children.length > 1) {
context.onError(
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
source: ''
})
)
}
}
}
let nodeType
if (dynamicComponent) {
nodeType = dynamicComponent
} else if (builtInComponentSymbol) {
nodeType = context.helper(builtInComponentSymbol)
} else if (isComponent) {
// user component w/ resolve
context.helper(RESOLVE_COMPONENT)
context.components.add(tag)
nodeType = toValidAssetId(tag, `component`)
} else {
// plain element
nodeType = `"${node.tag}"`
}
const args: CallExpression['arguments'] = [nodeType]
// props
if (hasProps) {
const propsBuildResult = buildProps(
node,
context,
// skip reserved "is" prop <component is>
isProp ? node.props.filter(p => p !== isProp) : node.props
)
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
runtimeDirectives = propsBuildResult.directives
if (!propsBuildResult.props) {
hasProps = false
} else {
args.push(propsBuildResult.props)
}
}
// children
const hasChildren = node.children.length > 0
if (hasChildren) {
if (!hasProps) {
args.push(`null`)
}
// Portal & KeepAlive should have normal children instead of slots
// Portal is not a real component has dedicated handling in the renderer
// KeepAlive should not track its own deps so that it can be used inside
// Transition
if (
const shouldBuildAsSlots =
isComponent &&
builtInComponentSymbol !== PORTAL &&
builtInComponentSymbol !== KEEP_ALIVE
) {
// Teleport is not a real component and has dedicated runtime handling
vnodeTag !== TELEPORT &&
// explained above.
vnodeTag !== KEEP_ALIVE
if (shouldBuildAsSlots) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
vnodeChildren = slots
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else if (node.children.length === 1) {
} else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
const child = node.children[0]
const type = child.type
// check for dynamic text children
@@ -158,65 +152,88 @@ export const transformElement: NodeTransform = (node, context) => {
// pass directly if the only child is a text node
// (plain / interpolation / expression)
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
args.push(child)
vnodeChildren = child as TemplateTextChildNode
} else {
args.push(node.children)
vnodeChildren = node.children
}
} else {
args.push(node.children)
vnodeChildren = node.children
}
}
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (!hasChildren) {
if (!hasProps) {
args.push(`null`)
}
args.push(`null`)
}
if (__DEV__) {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
args.push(patchFlag + ` /* ${flagNames} */`)
if (patchFlag < 0) {
// special flags (negative and mutually exclusive)
vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
} else {
// bitwise flags
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
}
} else {
args.push(patchFlag + '')
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
args.push(stringifyDynamicPropNames(dynamicPropNames))
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
}
}
const { loc } = node
const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc)
if (runtimeDirectives && runtimeDirectives.length) {
node.codegenNode = createCallExpression(
context.helper(WITH_DIRECTIVES),
[
vnode,
createArrayExpression(
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
loc
)
],
loc
)
} else {
node.codegenNode = vnode
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
vnodeDynamicProps,
vnodeDirectives,
!!shouldUseBlock,
false /* isForBlock */,
node.loc
)
}
}
function stringifyDynamicPropNames(props: string[]): string {
let propsNamesString = `[`
for (let i = 0, l = props.length; i < l; i++) {
propsNamesString += JSON.stringify(props[i])
if (i < l - 1) propsNamesString += ', '
export function resolveComponentType(
node: ComponentNode,
context: TransformContext,
ssr = false
) {
const { tag } = node
// 1. dynamic component
const isProp =
node.tag === 'component' ? findProp(node, 'is') : findDir(node, 'is')
if (isProp) {
const exp =
isProp.type === NodeTypes.ATTRIBUTE
? isProp.value && createSimpleExpression(isProp.value.content, true)
: isProp.exp
if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
exp
])
}
}
return propsNamesString + `]`
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
if (builtIn) {
// built-ins are simply fallthroughs / have special handling during ssr
// no we don't need to import their runtime equivalents
if (!ssr) context.helper(builtIn)
return builtIn
}
// 3. user component (resolve)
context.helper(RESOLVE_COMPONENT)
context.components.add(tag)
return toValidAssetId(tag, `component`)
}
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
@@ -224,14 +241,15 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props
props: ElementNode['props'] = node.props,
ssr = false
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
} {
const elementLoc = node.loc
const { tag, loc: elementLoc } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
@@ -242,27 +260,40 @@ export function buildProps(
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
let hasHydrationEventBinding = false
let hasDynamicKeys = false
const dynamicPropNames: string[] = []
const analyzePatchFlag = ({ key, value }: Property) => {
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
const name = key.content
if (
!isComponent &&
isOn(name) &&
// omit the flag for click handlers becaues hydration gives click
// dedicated fast path.
name.toLowerCase() !== 'onclick' &&
// omit v-model handlers
name !== 'onUpdate:modelValue'
) {
hasHydrationEventBinding = true
}
if (
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
isStaticNode(value))
) {
// skip if the prop is a cached handler or has constant value
return
}
const name = key.content
if (name === 'ref') {
hasRef = true
} else if (name === 'class') {
hasClassBinding = true
} else if (name === 'style') {
hasStyleBinding = true
} else if (name !== 'key') {
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
dynamicPropNames.push(name)
}
} else {
@@ -278,6 +309,10 @@ export function buildProps(
if (name === 'ref') {
hasRef = true
}
// skip :is on <component>
if (name === 'is' && tag === 'component') {
continue
}
properties.push(
createObjectProperty(
createSimpleExpression(
@@ -295,6 +330,8 @@ export function buildProps(
} else {
// directives
const { name, arg, exp, loc } = prop
const isBind = name === 'bind'
const isOn = name === 'on'
// skip v-slot - it is handled by its dedicated transform.
if (name === 'slot') {
@@ -305,15 +342,23 @@ export function buildProps(
}
continue
}
// skip v-once - it is handled by its dedicated transform.
if (name === 'once') {
continue
}
// skip v-is and :is on <component>
if (
name === 'is' ||
(isBind && tag === 'component' && isBindKey(arg, 'is'))
) {
continue
}
// skip v-on in SSR compilation
if (isOn && ssr) {
continue
}
// special case for v-bind and v-on with no argument
const isBind = name === 'bind'
const isOn = name === 'on'
if (!arg && (isBind || isOn)) {
hasDynamicKeys = true
if (exp) {
@@ -351,7 +396,7 @@ export function buildProps(
if (directiveTransform) {
// has built-in directive transform.
const { props, needRuntime } = directiveTransform(prop, node, context)
props.forEach(analyzePatchFlag)
!ssr && props.forEach(analyzePatchFlag)
properties.push(...props)
if (needRuntime) {
runtimeDirectives.push(prop)
@@ -405,8 +450,14 @@ export function buildProps(
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS
}
if (hasHydrationEventBinding) {
patchFlag |= PatchFlags.HYDRATE_EVENTS
}
}
if (patchFlag === 0 && (hasRef || runtimeDirectives.length > 0)) {
if (
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(hasRef || runtimeDirectives.length > 0)
) {
patchFlag |= PatchFlags.NEED_PATCH
}
@@ -437,12 +488,7 @@ function dedupeProperties(properties: Property[]): Property[] {
const name = prop.key.content
const existing = knownProps.get(name)
if (existing) {
if (
name === 'style' ||
name === 'class' ||
name.startsWith('on') ||
name.startsWith('vnode')
) {
if (name === 'style' || name === 'class' || name.startsWith('on')) {
mergeAsArray(existing, prop)
}
// unexpected duplicate, should have emitted error during parse
@@ -472,7 +518,6 @@ function buildDirectiveArgs(
const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir)
if (runtime) {
context.helper(runtime)
dirArgs.push(context.helperString(runtime))
} else {
// inject statement for resolving directive
@@ -507,3 +552,12 @@ function buildDirectiveArgs(
}
return createArrayExpression(dirArgs, dir.loc)
}
function stringifyDynamicPropNames(props: string[]): string {
let propsNamesString = `[`
for (let i = 0, l = props.length; i < l; i++) {
propsNamesString += JSON.stringify(props[i])
if (i < l - 1) propsNamesString += ', '
}
return propsNamesString + `]`
}

View File

@@ -16,7 +16,6 @@ import {
CompoundExpressionNode,
createCompoundExpression
} from '../ast'
import { Node, Function, Identifier, Property } from 'estree'
import {
advancePositionWithClone,
isSimpleIdentifier,
@@ -25,6 +24,7 @@ import {
} from '../utils'
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
@@ -40,11 +40,15 @@ export const transformExpression: NodeTransform = (node, context) => {
const dir = node.props[i]
// do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp as SimpleExpressionNode | undefined
const arg = dir.arg as SimpleExpressionNode | undefined
const exp = dir.exp
const arg = dir.arg
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (exp && !(dir.name === 'on' && arg)) {
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
dir.exp = processExpression(
exp,
context,
@@ -52,7 +56,7 @@ export const transformExpression: NodeTransform = (node, context) => {
dir.name === 'slot'
)
}
if (arg && !arg.isStatic) {
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
}
@@ -76,7 +80,9 @@ export function processExpression(
context: TransformContext,
// some expressions like v-slot props & v-for aliases should be parsed as
// function params
asParams: boolean = false
asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false
): ExpressionNode {
if (!context.prefixIdentifiers || !node.content.trim()) {
return node
@@ -84,6 +90,8 @@ export function processExpression(
// fast path if expression is a simple identifier.
const rawExp = node.content
// bail on parens to prevent any possible function invocations.
const bailConstant = rawExp.indexOf(`(`) > -1
if (isSimpleIdentifier(rawExp)) {
if (
!asParams &&
@@ -92,7 +100,7 @@ export function processExpression(
!isLiteralWhitelisted(rawExp)
) {
node.content = `_ctx.${rawExp}`
} else if (!context.identifiers[rawExp]) {
} else if (!context.identifiers[rawExp] && !bailConstant) {
// mark node constant for hoisting unless it's referring a scope variable
node.isConstant = true
}
@@ -100,26 +108,48 @@ export function processExpression(
}
let ast: any
// if the expression is supposed to be used in a function params position
// we need to parse it differently.
const source = `(${rawExp})${asParams ? `=>{}` : ``}`
// exp needs to be parsed differently:
// 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
// exp, but make sure to pad with spaces for consistent ranges
// 2. Expressions: wrap with parens (for e.g. object expressions)
// 3. Function arguments (v-for, v-slot): place in a function argument position
const source = asRawStatements
? ` ${rawExp} `
: `(${rawExp})${asParams ? `=>{}` : ``}`
try {
ast = parseJS(source, { ranges: true })
ast = parseJS(source, {
plugins: [
...context.expressionPlugins,
// by default we enable proposals slated for ES2020.
// full list at https://babeljs.io/docs/en/next/babel-parser#plugins
// this will need to be updated as the spec moves forward.
'bigInt',
'optionalChaining',
'nullishCoalescingOperator'
]
}).program
} catch (e) {
context.onError(
createCompilerError(ErrorCodes.X_INVALID_EXPRESSION, node.loc)
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
e.message
)
)
return node
}
const ids: (Identifier & PrefixMeta)[] = []
const knownIds = Object.create(context.identifiers)
const isDuplicate = (node: Node & PrefixMeta): boolean =>
ids.some(id => id.start === node.start)
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
walkJS(ast, {
enter(node: Node & PrefixMeta, parent) {
if (node.type === 'Identifier') {
if (!ids.includes(node)) {
if (!isDuplicate(node)) {
const needPrefix = shouldPrefix(node, parent)
if (!knownIds[node.name] && needPrefix) {
if (isPropertyShorthand(node, parent)) {
@@ -128,12 +158,13 @@ export function processExpression(
node.prefix = `${node.name}: `
}
node.name = `_ctx.${node.name}`
node.isConstant = false
ids.push(node)
} else if (!isStaticPropertyKey(node, parent)) {
// The identifier is considered constant unless it's pointing to a
// scope variable (a v-for alias, or a v-slot prop)
node.isConstant = !(needPrefix && knownIds[node.name])
if (!(needPrefix && knownIds[node.name]) && !bailConstant) {
node.isConstant = true
}
// also generate sub-expressions for other identifiers for better
// source map support. (except for property keys which are static)
ids.push(node)
@@ -223,7 +254,7 @@ export function processExpression(
ret = createCompoundExpression(children, node.loc)
} else {
ret = node
ret.isConstant = true
ret.isConstant = !bailConstant
}
ret.identifiers = Object.keys(knownIds)
return ret
@@ -232,17 +263,21 @@ export function processExpression(
const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type)
const isPropertyKey = (node: Node, parent: Node) =>
parent &&
parent.type === 'Property' &&
parent.key === node &&
!parent.computed
const isStaticProperty = (node: Node): node is ObjectProperty =>
node && node.type === 'ObjectProperty' && !node.computed
const isPropertyShorthand = (node: Node, parent: Node) =>
isPropertyKey(node, parent) && (parent as Property).value === node
const isPropertyShorthand = (node: Node, parent: Node) => {
return (
isStaticProperty(parent) &&
parent.value === node &&
parent.key.type === 'Identifier' &&
parent.key.name === (node as Identifier).name &&
parent.key.start === node.start
)
}
const isStaticPropertyKey = (node: Node, parent: Node) =>
isPropertyKey(node, parent) && (parent as Property).value !== node
isStaticProperty(parent) && parent.key === node
function shouldPrefix(identifier: Identifier, parent: Node) {
if (
@@ -257,7 +292,8 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
!isStaticPropertyKey(identifier, parent) &&
// not a property of a MemberExpression
!(
parent.type === 'MemberExpression' &&
(parent.type === 'MemberExpression' ||
parent.type === 'OptionalMemberExpression') &&
parent.property === identifier &&
!parent.computed
) &&

View File

@@ -1,78 +1,32 @@
import { NodeTransform } from '../transform'
import { NodeTransform, TransformContext } from '../transform'
import {
NodeTypes,
CallExpression,
createCallExpression,
ExpressionNode
ExpressionNode,
SlotOutletNode
} from '../ast'
import { isSlotOutlet } from '../utils'
import { buildProps } from './transformElement'
import { isSlotOutlet, findProp } from '../utils'
import { buildProps, PropsExpression } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers'
export const transformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) {
const { props, children, loc } = node
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
let slotName: string | ExpressionNode = `"default"`
const { children, loc } = node
const { slotName, slotProps } = processSlotOutlet(node, context)
// check for <slot name="xxx" OR :name="xxx" />
let nameIndex: number = -1
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
if (prop.name === `name` && prop.value) {
// static name="xxx"
slotName = JSON.stringify(prop.value.content)
nameIndex = i
break
}
} else if (prop.name === `bind`) {
const { arg, exp } = prop
if (
arg &&
exp &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.isStatic &&
arg.content === `name`
) {
// dynamic :name="xxx"
slotName = exp
nameIndex = i
break
}
}
}
const slotArgs: CallExpression['arguments'] = [
context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
slotName
]
const slotArgs: CallExpression['arguments'] = [$slots, slotName]
const propsWithoutName =
nameIndex > -1
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
: props
let hasProps = propsWithoutName.length > 0
if (hasProps) {
const { props: propsExpression, directives } = buildProps(
node,
context,
propsWithoutName
)
if (directives.length) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc
)
)
}
if (propsExpression) {
slotArgs.push(propsExpression)
} else {
hasProps = false
}
if (slotProps) {
slotArgs.push(slotProps)
}
if (children.length) {
if (!hasProps) {
if (!slotProps) {
slotArgs.push(`{}`)
}
slotArgs.push(children)
@@ -85,3 +39,49 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
)
}
}
interface SlotOutletProcessResult {
slotName: string | ExpressionNode
slotProps: PropsExpression | undefined
}
export function processSlotOutlet(
node: SlotOutletNode,
context: TransformContext
): SlotOutletProcessResult {
let slotName: string | ExpressionNode = `"default"`
let slotProps: PropsExpression | undefined = undefined
// check for <slot name="xxx" OR :name="xxx" />
const name = findProp(node, 'name')
if (name) {
if (name.type === NodeTypes.ATTRIBUTE && name.value) {
// static name
slotName = JSON.stringify(name.value.content)
} else if (name.type === NodeTypes.DIRECTIVE && name.exp) {
// dynamic name
slotName = name.exp
}
}
const propsWithoutName = name
? node.props.filter(p => p !== name)
: node.props
if (propsWithoutName.length > 0) {
const { props, directives } = buildProps(node, context, propsWithoutName)
slotProps = props
if (directives.length) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc
)
)
}
}
return {
slotName,
slotProps
}
}

View File

@@ -1,29 +1,23 @@
import { NodeTransform } from '../transform'
import {
NodeTypes,
TemplateChildNode,
TextNode,
InterpolationNode,
CompoundExpressionNode,
createCallExpression,
CallExpression,
ElementTypes
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
const isText = (
node: TemplateChildNode
): node is TextNode | InterpolationNode =>
node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
export const transformText: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ROOT ||
node.type === NodeTypes.ELEMENT ||
node.type === NodeTypes.FOR
node.type === NodeTypes.FOR ||
node.type === NodeTypes.IF_BRANCH
) {
// perform the transform on node exit so that all expressions have already
// been processed.
@@ -84,7 +78,7 @@ export const transformText: NodeTransform = (node, context) => {
callArgs.push(child)
}
// mark dynamic text with flag so it gets patched inside a block
if (child.type !== NodeTypes.TEXT) {
if (!context.ssr && child.type !== NodeTypes.TEXT) {
callArgs.push(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
)

View File

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

View File

@@ -8,22 +8,28 @@ import {
createSimpleExpression,
SourceLocation,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createFunctionExpression,
ElementTypes,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
SlotOutletNode
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode,
createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
getInnerRange,
findProp,
createBlockExpression,
isTemplateNode,
isSlotOutlet,
injectProp
@@ -32,8 +38,7 @@ import {
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES
FRAGMENT
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
@@ -41,141 +46,163 @@ import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, context) => {
if (!dir.exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
)
return
}
const parseResult = parseForExpression(
// can only be simple expression because vFor transform is applied
// before expression transform.
dir.exp as SimpleExpressionNode,
context
)
if (!parseResult) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
)
return
}
const { helper, addIdentifiers, removeIdentifiers, scopes } = context
const { source, value, key, index } = parseResult
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [source])
const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
const codegenNode = createSequenceExpression([
// fragment blocks disable tracking since they always diff their children
createCallExpression(helper(OPEN_BLOCK), [`false`]),
createCallExpression(helper(CREATE_BLOCK), [
const { helper } = context
return processFor(node, dir, context, forNode => {
// create the loop render function expression now, and add the
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source
]) as ForRenderListExpression
const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
`null`,
undefined,
renderExp,
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
])
]) as ForCodegenNode
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`,
undefined,
undefined,
true /* isBlock */,
true /* isForBlock */,
node.loc
) as ForCodegenNode
context.replaceNode({
type: NodeTypes.FOR,
loc: dir.loc,
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
codegenNode
})
// bookkeeping
scopes.vFor++
if (!__BROWSER__ && context.prefixIdentifiers) {
// scope management
// inject identifiers to context
value && addIdentifiers(value)
key && addIdentifiers(key)
index && addIdentifiers(index)
}
return () => {
scopes.vFor--
if (!__BROWSER__ && context.prefixIdentifiers) {
value && removeIdentifiers(value)
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
// finish the codegen now that all children have been traversed
let childBlock
const isTemplate = isTemplateNode(node)
const slotOutlet = isSlotOutlet(node)
? node
: isTemplate &&
node.children.length === 1 &&
isSlotOutlet(node.children[0])
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
return () => {
// finish the codegen now that all children have been traversed
let childBlock: BlockCodegenNode
const isTemplate = isTemplateNode(node)
const { children } = forNode
const needFragmentWrapper =
children.length > 1 || children[0].type !== NodeTypes.ELEMENT
const slotOutlet = isSlotOutlet(node)
? node
: isTemplate &&
node.children.length === 1 &&
isSlotOutlet(node.children[0])
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
: null
const keyProperty = keyProp
? createObjectProperty(
`key`,
keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!
)
: null
const keyProperty = keyProp
? createObjectProperty(
`key`,
keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!
)
: null
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context)
}
} else if (isTemplate) {
// <template v-for="...">
// should generate a fragment block for each loop
childBlock = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as RenderSlotCall
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context)
}
} else if (needFragmentWrapper) {
// <template v-for="..."> with text or multi-elements
// should generate a fragment block for each loop
childBlock = createVNodeCall(
context,
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : `null`,
keyProperty ? createObjectExpression([keyProperty]) : undefined,
node.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
)
} else {
// Normal element v-for. Directly use the child's codegenNode
// arguments, but replace createVNode() with createBlock()
let codegenNode = node.codegenNode as ElementCodegenNode
if (codegenNode.callee === WITH_DIRECTIVES) {
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
} */`,
undefined,
undefined,
true
)
} else {
codegenNode.callee = helper(CREATE_BLOCK)
// Normal element v-for. Directly use the child's codegenNode
// but mark it as a block.
childBlock = (children[0] as PlainElementNode)
.codegenNode as VNodeCall
childBlock.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
childBlock = createBlockExpression(codegenNode, context)
}
renderExp.arguments.push(
createFunctionExpression(
createForLoopParams(parseResult),
renderExp.arguments.push(createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
true /* force newline */
)
)
}
) as ForIteratorExpression)
}
})
}
)
// target-agnostic transform used for both Client and SSR
export function processFor(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (forNode: ForNode) => (() => void) | undefined
) {
if (!dir.exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
)
return
}
const parseResult = parseForExpression(
// can only be simple expression because vFor transform is applied
// before expression transform.
dir.exp as SimpleExpressionNode,
context
)
if (!parseResult) {
context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
)
return
}
const { addIdentifiers, removeIdentifiers, scopes } = context
const { source, value, key, index } = parseResult
const forNode: ForNode = {
type: NodeTypes.FOR,
loc: dir.loc,
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
parseResult,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
}
context.replaceNode(forNode)
// bookkeeping
scopes.vFor++
if (!__BROWSER__ && context.prefixIdentifiers) {
// scope management
// inject identifiers to context
value && addIdentifiers(value)
key && addIdentifiers(key)
index && addIdentifiers(index)
}
const onExit = processCodegen && processCodegen(forNode)
return () => {
scopes.vFor--
if (!__BROWSER__ && context.prefixIdentifiers) {
value && removeIdentifiers(value)
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
if (onExit) onExit()
}
}
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
// This regex doesn't cover the case if key or index aliases have destructuring,
// but those do not make sense in the first place, so this works in practice.

View File

@@ -1,7 +1,7 @@
import {
createStructuralDirectiveTransform,
traverseChildren,
TransformContext
TransformContext,
traverseNode
} from '../transform'
import {
NodeTypes,
@@ -10,130 +10,139 @@ import {
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression,
IfCodegenNode,
IfConditionalExpression,
BlockCodegenNode,
SlotOutletCodegenNode,
ElementCodegenNode,
ComponentCodegenNode
IfNode,
createVNodeCall
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES,
CREATE_VNODE,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK,
TELEPORT
} from '../runtimeHelpers'
import { injectProp } from '../utils'
import { PatchFlags, PatchFlagNames } 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_V_IF_NO_EXPRESSION, dir.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.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK))
]) as IfCodegenNode
context.replaceNode({
type: NodeTypes.IF,
loc: node.loc,
branches: [branch],
codegenNode
})
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
codegenNode.expressions.push(createCodegenNodeForBranch(
branch,
0,
context
) as IfConditionalExpression)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
const comments = []
let i = siblings.indexOf(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
comments.unshift(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// move the node to the if node's branches
context.removeNode()
const branch = createIfBranch(node, dir)
if (__DEV__ && comments.length) {
branch.children = [...comments, ...branch.children]
}
sibling.branches.push(branch)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseChildren(branch, context)
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
// 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(
branch,
sibling.branches.length - 1,
context
)
break
}
}
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
0,
context
) as IfConditionalExpression
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
// attach this branch's codegen node to the v-if root.
let parentCondition = ifNode.codegenNode!
while (
parentCondition.alternate.type ===
NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
parentCondition = parentCondition.alternate
}
parentCondition.alternate = createCodegenNodeForBranch(
branch,
ifNode.branches.length - 1,
context
)
}
break
}
}
})
}
)
// target-agnostic transform used for both Client and SSR
export function processIf(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (
node: IfNode,
branch: IfBranchNode,
isRoot: boolean
) => (() => void) | undefined
) {
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_V_IF_NO_EXPRESSION, dir.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.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch]
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
const comments = []
let i = siblings.indexOf(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
comments.unshift(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// move the node to the if node's branches
context.removeNode()
const branch = createIfBranch(node, dir)
if (__DEV__ && comments.length) {
branch.children = [...comments, ...branch.children]
}
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseNode(branch, context)
// call on exit
if (onExit) onExit()
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break
}
}
}
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
return {
type: NodeTypes.IF_BRANCH,
@@ -160,7 +169,7 @@ function createCodegenNodeForBranch(
])
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
return createChildrenCodegenNode(branch, index, context)
}
}
@@ -168,46 +177,56 @@ function createChildrenCodegenNode(
branch: IfBranchNode,
index: number,
context: TransformContext
): CallExpression {
): BlockCodegenNode {
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(index + '', false)
)
const { children } = branch
const child = children[0]
const firstChild = children[0]
const needFragmentWrapper =
children.length !== 1 || child.type !== NodeTypes.ELEMENT
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children
]
if (children.length === 1 && child.type === NodeTypes.FOR) {
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
const forBlockArgs = child.codegenNode.expressions[1].arguments
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3]
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} else {
return createVNodeCall(
context,
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`,
undefined,
undefined,
true,
false,
branch.loc
)
}
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
const childCodegen = (child as ElementNode).codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
let vnodeCall = childCodegen
// Element with custom directives. Locate the actual createVNode() call.
if (vnodeCall.callee === WITH_DIRECTIVES) {
vnodeCall = vnodeCall.arguments[0]
}
const vnodeCall = (firstChild as ElementNode)
.codegenNode as BlockCodegenNode
// Change createVNode to createBlock.
if (vnodeCall.callee === CREATE_VNODE) {
vnodeCall.callee = helper(CREATE_BLOCK)
if (
vnodeCall.type === NodeTypes.VNODE_CALL &&
// component vnodes are always tracked and its children are
// compiled into slots so no need to make it a block
((firstChild as ElementNode).tagType !== ElementTypes.COMPONENT ||
// teleport has component type but isn't always tracked
vnodeCall.tag === TELEPORT)
) {
vnodeCall.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
// inject branch key
injectProp(vnodeCall, keyProperty, context)
return childCodegen
return vnodeCall
}
}

View File

@@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const eventName = arg
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
? `onUpdate:${arg.content}`
: createCompoundExpression([
'"onUpdate:" + ',
...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
])
: createCompoundExpression(['"onUpdate:" + ', arg])
: `onUpdate:modelValue`
const props = [
@@ -56,11 +53,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
// "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty(
eventName,
createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
` = $event)`
])
createCompoundExpression([`$event => (`, exp, ` = $event)`])
)
]
@@ -82,12 +75,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const modifiersKey = arg
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
? `${arg.content}Modifiers`
: createCompoundExpression([
...(arg.type === NodeTypes.SIMPLE_EXPRESSION
? [arg]
: arg.children),
' + "Modifiers"'
])
: createCompoundExpression([arg, ' + "Modifiers"'])
: `modelModifiers`
props.push(
createObjectProperty(
@@ -101,5 +89,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
}
function createTransformProps(props: Property[] = []) {
return { props, needRuntime: false }
return { props }
}

View File

@@ -8,7 +8,7 @@ import {
createCompoundExpression,
SimpleExpressionNode
} from '../ast'
import { capitalize } from '@vue/shared'
import { capitalize, camelize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { isMemberExpression, hasScopeRef } from '../utils'
@@ -26,23 +26,24 @@ export interface VOnDirectiveNode extends DirectiveNode {
}
export const transformOn: DirectiveTransform = (
dir: VOnDirectiveNode,
dir,
node,
context,
augmentor
) => {
const { loc, modifiers, arg } = dir
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
eventName = createSimpleExpression(
`on${capitalize(arg.content)}`,
true,
arg.loc
)
const rawName = arg.content
// for @vnode-xxx event listeners, auto convert it to camelCase
const normalizedName = rawName.startsWith(`vnode`)
? capitalize(camelize(rawName))
: capitalize(rawName)
eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc)
} else {
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
}
@@ -54,16 +55,22 @@ export const transformOn: DirectiveTransform = (
}
// handler processing
let exp: ExpressionNode | undefined = dir.exp
let exp: ExpressionNode | undefined = dir.exp as
| SimpleExpressionNode
| undefined
if (exp && !exp.content.trim()) {
exp = undefined
}
let isCacheable: boolean = !exp
if (exp) {
const isMemberExp = isMemberExpression(exp.content)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
// process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`)
exp = processExpression(exp, context)
exp = processExpression(exp, context, false, hasMultipleStatements)
context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference
// to scope variables.
@@ -85,9 +92,9 @@ export const transformOn: DirectiveTransform = (
if (isInlineStatement || (isCacheable && isMemberExp)) {
// wrap inline statement in a function expression
exp = createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
`)`
`$event => ${hasMultipleStatements ? `{` : `(`}`,
exp,
hasMultipleStatements ? `}` : `)`
])
}
}
@@ -98,8 +105,7 @@ export const transformOn: DirectiveTransform = (
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
)
],
needRuntime: false
]
}
// apply extended compiler augmentor

View File

@@ -19,12 +19,13 @@ import {
FunctionExpression,
CallExpression,
createCallExpression,
createArrayExpression
createArrayExpression,
SlotsExpression
} from '../ast'
import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
import { findDir, isTemplateNode, assert, isVSlot, hasScopeRef } from '../utils'
import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers'
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
import { parseForExpression, createForLoopParams } from './vFor'
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
@@ -93,19 +94,42 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
}
}
export type SlotFnBuilder = (
slotProps: ExpressionNode | undefined,
slotChildren: TemplateChildNode[],
loc: SourceLocation
) => FunctionExpression
const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
createFunctionExpression(
props,
children,
false /* newline */,
true /* isSlot */,
children.length ? children[0].loc : loc
)
// Instead of being a DirectiveTransform, v-slot processing is called during
// transformElement to build the slots object for a component.
export function buildSlots(
node: ElementNode,
context: TransformContext
context: TransformContext,
buildSlotFn: SlotFnBuilder = buildClientSlotFn
): {
slots: ObjectExpression | CallExpression
slots: SlotsExpression
hasDynamicSlots: boolean
} {
context.helper(WITH_CTX)
const { children, loc } = node
const slotsProperties: Property[] = []
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
const buildDefaultSlotProperty = (
props: ExpressionNode | undefined,
children: TemplateChildNode[]
) => createObjectProperty(`default`, buildSlotFn(props, children, loc))
// If the slot is inside a v-for or another v-slot, force it to be dynamic
// since it likely uses a scope variable.
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
@@ -115,24 +139,26 @@ export function buildSlots(
hasDynamicSlots = hasScopeRef(node, context.identifiers)
}
// 1. Check for default slot with slotProps on component itself.
// 1. Check for slot with slotProps on component itself.
// <Comp v-slot="{ prop }"/>
const explicitDefaultSlot = findDir(node, 'slot', true)
if (explicitDefaultSlot) {
const { arg, exp, loc } = explicitDefaultSlot
if (arg) {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
const onComponentSlot = findDir(node, 'slot', true)
if (onComponentSlot) {
const { arg, exp } = onComponentSlot
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
buildSlotFn(exp, children, loc)
)
}
slotsProperties.push(buildDefaultSlot(exp, children, loc))
)
}
// 2. Iterate through children and check for template slots
// <template v-slot:foo="{ prop }">
let hasTemplateSlots = false
let extraneousChild: TemplateChildNode | undefined = undefined
let hasNamedDefaultSlot = false
const implicitDefaultChildren: TemplateChildNode[] = []
const seenSlotNames = new Set<string>()
for (let i = 0; i < children.length; i++) {
const slotElement = children[i]
let slotDir
@@ -142,14 +168,14 @@ export function buildSlots(
!(slotDir = findDir(slotElement, 'slot', true))
) {
// not a <template v-slot>, skip.
if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
extraneousChild = slotElement
if (slotElement.type !== NodeTypes.COMMENT) {
implicitDefaultChildren.push(slotElement)
}
continue
}
if (explicitDefaultSlot) {
// already has on-component default slot - this is incorrect usage.
if (onComponentSlot) {
// already has on-component slot - this is incorrect usage.
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
)
@@ -172,13 +198,7 @@ export function buildSlots(
hasDynamicSlots = true
}
const slotFunction = createFunctionExpression(
slotProps,
slotChildren,
false,
slotChildren.length ? slotChildren[0].loc : slotLoc
)
const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
// check if this slot is conditional (v-if/v-for)
let vIf: DirectiveNode | undefined
let vElse: DirectiveNode | undefined
@@ -244,7 +264,7 @@ export function buildSlots(
createFunctionExpression(
createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction),
true
true /* force newline */
)
])
)
@@ -266,36 +286,46 @@ export function buildSlots(
continue
}
seenSlotNames.add(staticSlotName)
if (staticSlotName === 'default') {
hasNamedDefaultSlot = true
}
}
slotsProperties.push(createObjectProperty(slotName, slotFunction))
}
}
if (hasTemplateSlots && extraneousChild) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
extraneousChild.loc
)
)
if (!onComponentSlot) {
if (!hasTemplateSlots) {
// implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
} else if (implicitDefaultChildren.length) {
// implicit default slot (mixed with named slots)
if (hasNamedDefaultSlot) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
implicitDefaultChildren[0].loc
)
)
} else {
slotsProperties.push(
buildDefaultSlotProperty(undefined, implicitDefaultChildren)
)
}
}
}
if (!explicitDefaultSlot && !hasTemplateSlots) {
// implicit default slot.
slotsProperties.push(buildDefaultSlot(undefined, children, loc))
}
let slots: ObjectExpression | CallExpression = createObjectExpression(
let slots = createObjectExpression(
slotsProperties.concat(
createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
createObjectProperty(`_`, createSimpleExpression(`1`, false))
),
loc
)
) as SlotsExpression
if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots,
createArrayExpression(dynamicSlots)
])
]) as SlotsExpression
}
return {
@@ -304,22 +334,6 @@ export function buildSlots(
}
}
function buildDefaultSlot(
slotProps: ExpressionNode | undefined,
children: TemplateChildNode[],
loc: SourceLocation
): Property {
return createObjectProperty(
`default`,
createFunctionExpression(
slotProps,
children,
false,
children.length ? children[0].loc : loc
)
)
}
function buildDynamicSlot(
name: ExpressionNode,
fn: FunctionExpression

View File

@@ -4,8 +4,6 @@ import {
ElementNode,
NodeTypes,
CallExpression,
SequenceExpression,
createSequenceExpression,
createCallExpression,
DirectiveNode,
ElementTypes,
@@ -17,33 +15,31 @@ import {
createObjectExpression,
SlotOutletNode,
TemplateNode,
BlockCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
ComponentCodegenNode,
RenderSlotCall,
ExpressionNode,
IfBranchNode
IfBranchNode,
TextNode,
InterpolationNode,
VNodeCall
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import {
OPEN_BLOCK,
MERGE_PROPS,
RENDER_SLOT,
PORTAL,
TELEPORT,
SUSPENSE,
KEEP_ALIVE,
BASE_TRANSITION
} from './runtimeHelpers'
import { isString, isFunction, isObject, hyphenate } from '@vue/shared'
import { parse } from '@babel/parser'
import { Node } from '@babel/types'
export const isBuiltInType = (tag: string, expected: string): boolean =>
tag === expected || tag === hyphenate(expected)
export function isCoreComponent(tag: string): symbol | void {
if (isBuiltInType(tag, 'Portal')) {
return PORTAL
if (isBuiltInType(tag, 'Teleport')) {
return TELEPORT
} else if (isBuiltInType(tag, 'Suspense')) {
return SUSPENSE
} else if (isBuiltInType(tag, 'KeepAlive')) {
@@ -57,10 +53,10 @@ export function isCoreComponent(tag: string): symbol | void {
// lazy require dependencies so that they don't end up in rollup's dep graph
// and thus can be tree-shaken in browser builds.
let _parse: typeof parse
let _walk: typeof walk
let _walk: any
export function loadDep(name: string) {
if (typeof process !== 'undefined' && isFunction(require)) {
if (!__BROWSER__ && typeof process !== 'undefined' && isFunction(require)) {
return require(name)
} else {
// This is only used when we are building a dev-only build of the compiler
@@ -74,11 +70,18 @@ export const parseJS: typeof parse = (code, options) => {
!__BROWSER__,
`Expression AST analysis can only be performed in non-browser builds.`
)
const parse = _parse || (_parse = loadDep('acorn').parse)
return parse(code, options)
if (!_parse) {
_parse = loadDep('@babel/parser').parse
}
return _parse(code, options)
}
export const walkJS: typeof walk = (ast, walker) => {
interface Walker {
enter?(node: Node, parent: Node): void
leave?(node: Node): void
}
export const walkJS = (ast: Node, walker: Walker) => {
assert(
!__BROWSER__,
`Expression AST analysis can only be performed in non-browser builds.`
@@ -149,7 +152,7 @@ export function advancePositionWithMutation(
pos.column =
lastNewLinePos === -1
? pos.column + numberOfCharacters
: Math.max(1, numberOfCharacters - lastNewLinePos)
: numberOfCharacters - lastNewLinePos
return pos
}
@@ -181,36 +184,46 @@ export function findDir(
export function findProp(
node: ElementNode,
name: string,
dynamicOnly: boolean = false
dynamicOnly: boolean = false,
allowEmpty: boolean = false
): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (dynamicOnly) continue
if (p.name === name && p.value) {
if (p.name === name && (p.value || allowEmpty)) {
return p
}
} else if (
p.name === 'bind' &&
p.arg &&
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
p.arg.isStatic &&
p.arg.content === name &&
p.exp
) {
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
return p
}
}
}
export function createBlockExpression(
blockExp: BlockCodegenNode,
context: TransformContext
): SequenceExpression {
return createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
blockExp
])
export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
return !!(
arg &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.isStatic &&
arg.content === name
)
}
export function hasDynamicKeyVBind(node: ElementNode): boolean {
return node.props.some(
p =>
p.type === NodeTypes.DIRECTIVE &&
p.name === 'bind' &&
(!p.arg || // v-bind="obj"
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
!p.arg.isStatic) // v-bind:[foo]
)
}
export function isText(
node: TemplateChildNode
): node is TextNode | InterpolationNode {
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
}
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
@@ -232,13 +245,13 @@ export function isSlotOutlet(
}
export function injectProp(
node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
node: VNodeCall | RenderSlotCall,
prop: Property,
context: TransformContext
) {
let propsWithInjection: ObjectExpression | CallExpression
const props =
node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
if (props == null || isString(props)) {
propsWithInjection = createObjectExpression([prop])
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
@@ -253,7 +266,19 @@ export function injectProp(
}
propsWithInjection = props
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
props.properties.unshift(prop)
let alreadyExists = false
// check existing key to avoid overriding user provided keys
if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
const propKeyName = prop.key.content
alreadyExists = props.properties.some(
p =>
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
p.key.content === propKeyName
)
}
if (!alreadyExists) {
props.properties.unshift(prop)
}
propsWithInjection = props
} else {
// single v-bind with expression, return a merged replacement
@@ -262,10 +287,10 @@ export function injectProp(
props
])
}
if (node.callee === RENDER_SLOT) {
node.arguments[2] = propsWithInjection
if (node.type === NodeTypes.VNODE_CALL) {
node.props = propsWithInjection
} else {
node.arguments[1] = propsWithInjection
node.arguments[2] = propsWithInjection
}
}