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:
@@ -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
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -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 */))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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\\"))
|
||||
})"
|
||||
`;
|
||||
@@ -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]
|
||||
])
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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('&ersand;', {
|
||||
const ast = baseParse('&ersand;', {
|
||||
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="&ersand;" b="&ersand;" c="&!"></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('†', { onError: spy })
|
||||
const ast = baseParse('†', { 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('&∪︀', {
|
||||
const ast: any = baseParse('&∪︀', {
|
||||
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) {
|
||||
|
||||
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal file
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
])
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -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)
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -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 */))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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` }]
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
CompilerOptions,
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
ElementNode,
|
||||
NodeTypes,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
CompilerOptions,
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
NodeTypes,
|
||||
generate,
|
||||
|
||||
@@ -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` },
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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` }, `++`] },
|
||||
`)`
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
100
packages/compiler-core/src/compile.ts
Normal file
100
packages/compiler-core/src/compile.ts
Normal 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
|
||||
})
|
||||
}
|
||||
@@ -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.`
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
|
||||
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
|
||||
@@ -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 + `]`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) &&
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]} */`
|
||||
)
|
||||
|
||||
@@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
|
||||
return {
|
||||
props: [
|
||||
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
|
||||
exports[`compile should contain standard transforms 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createVNode: _createVNode } = Vue
|
||||
|
||||
const _hoisted_1 = {}
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"div\\", { textContent: text }, null, 8 /* PROPS */, [\\"textContent\\"]),
|
||||
_createVNode(\\"div\\", { innerHTML: html }, null, 8 /* PROPS */, [\\"innerHTML\\"]),
|
||||
_createVNode(\\"div\\", null, \\"test\\"),
|
||||
_createVNode(\\"div\\", { style: _hoisted_1 }, \\"red\\"),
|
||||
_createVNode(\\"div\\", { style: {\\"color\\":\\"red\\"} }, \\"red\\"),
|
||||
_createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('compile', () => {
|
||||
const { code } = compile(`<div v-text="text"></div>
|
||||
<div v-html="html"></div>
|
||||
<div v-cloak>test</div>
|
||||
<div style="color=red">red</div>
|
||||
<div style="color:red">red</div>
|
||||
<div :style="{color: 'green'}"></div>`)
|
||||
|
||||
expect(code).toMatchSnapshot()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
baseParse as parse,
|
||||
NodeTypes,
|
||||
ElementNode,
|
||||
TextNode,
|
||||
@@ -49,6 +49,22 @@ describe('DOM parser', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('textarea support interpolation', () => {
|
||||
const ast = parse('<textarea><div>{{ foo }}</textarea>', parserOptions)
|
||||
const element = ast.children[0] as ElementNode
|
||||
expect(element.children).toMatchObject([
|
||||
{ type: NodeTypes.TEXT, content: `<div>` },
|
||||
{
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
content: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `foo`,
|
||||
isStatic: false
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('style handles comments/elements as just a text', () => {
|
||||
const ast = parse(
|
||||
'<style>some<div>text</div>and<!--comment--></style>',
|
||||
@@ -100,11 +116,36 @@ describe('DOM parser', () => {
|
||||
})
|
||||
|
||||
test('<pre> tag should preserve raw whitespace', () => {
|
||||
const rawText = ` \na b \n c`
|
||||
const rawText = ` \na <div>foo \n bar</div> \n c`
|
||||
const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
|
||||
expect((ast.children[0] as ElementNode).children).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: ` \na `
|
||||
},
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
children: [
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: `foo \n bar`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: ` \n c`
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// #908
|
||||
test('<pre> tag should remove leading newline', () => {
|
||||
const rawText = `\nhello`
|
||||
const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
|
||||
expect((ast.children[0] as ElementNode).children[0]).toMatchObject({
|
||||
type: NodeTypes.TEXT,
|
||||
content: rawText
|
||||
content: rawText.slice(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,23 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler: transform v-model input w/ dynamic v-bind 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelDynamic: _vModelDynamic, mergeProps: _mergeProps, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", _mergeProps(obj, {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}), null, 16 /* FULL_PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelDynamic, model]
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model input w/ dynamic v-bind 2`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
const _directive_bind = _resolveDirective(\\"bind\\")
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_directive_bind, val, key],
|
||||
[_vModelDynamic, model]
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model modifiers .lazy 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[
|
||||
_vModelText,
|
||||
model,
|
||||
void 0,
|
||||
{ lazy: true }
|
||||
]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -25,21 +59,20 @@ return function render() {
|
||||
exports[`compiler: transform v-model modifiers .number 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[
|
||||
_vModelText,
|
||||
model,
|
||||
void 0,
|
||||
{ number: true }
|
||||
]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -47,21 +80,20 @@ return function render() {
|
||||
exports[`compiler: transform v-model modifiers .trim 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[
|
||||
_vModelText,
|
||||
model,
|
||||
void 0,
|
||||
{ trim: true }
|
||||
]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -69,16 +101,15 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelText, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -86,17 +117,16 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for input (checkbox) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
type: \\"checkbox\\",
|
||||
modelValue: model,
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelCheckbox, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -104,19 +134,18 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for input (dynamic type) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
const _directive_bind = _resolveDirective(\\"bind\\")
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_directive_bind, foo, \\"type\\"],
|
||||
[_vModelDynamic, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -124,17 +153,16 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for input (radio) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
type: \\"radio\\",
|
||||
modelValue: model,
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelRadio, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -142,17 +170,16 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for input (text) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||
type: \\"text\\",
|
||||
modelValue: model,
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelText, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -160,16 +187,15 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for select 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"select\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"select\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelSelect, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -177,16 +203,15 @@ return function render() {
|
||||
exports[`compiler: transform v-model simple expression for textarea 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"textarea\\", {
|
||||
modelValue: model,
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"textarea\\", {
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
||||
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||
[_vModelText, model]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
exports[`compiler: v-show transform simple expression 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
|
||||
[_vShow, a]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { compile, NodeTypes, CREATE_STATIC } from '../../src'
|
||||
import {
|
||||
stringifyStatic,
|
||||
StringifyThresholds
|
||||
} from '../../src/transforms/stringifyStatic'
|
||||
|
||||
describe('stringify static html', () => {
|
||||
function compileWithStringify(template: string) {
|
||||
return compile(template, {
|
||||
hoistStatic: true,
|
||||
prefixIdentifiers: true,
|
||||
transformHoist: stringifyStatic
|
||||
})
|
||||
}
|
||||
|
||||
function repeat(code: string, n: number): string {
|
||||
return new Array(n)
|
||||
.fill(0)
|
||||
.map(() => code)
|
||||
.join('')
|
||||
}
|
||||
|
||||
test('should bail on non-eligible static trees', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div><div>hello</div><div>hello</div></div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be a normal vnode call
|
||||
expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements with binding > 5)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo"/>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements > 20)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span/>`,
|
||||
StringifyThresholds.NODE_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span></span>`,
|
||||
StringifyThresholds.NODE_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('serliazing constant bindings', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div :style="{ color: 'red' }">${repeat(
|
||||
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div style="color:red;">${repeat(
|
||||
`<span class="foo bar">1 + false</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('escape', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
|
||||
`<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
CompilerOptions,
|
||||
ElementNode,
|
||||
NodeTypes,
|
||||
CallExpression
|
||||
VNodeCall
|
||||
} from '@vue/compiler-core'
|
||||
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
@@ -26,17 +26,8 @@ function transformWithStyleTransform(
|
||||
}
|
||||
|
||||
describe('compiler: style transform', () => {
|
||||
test('should transform into directive node and hoist value', () => {
|
||||
const { root, node } = transformWithStyleTransform(
|
||||
`<div style="color: red"/>`
|
||||
)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `{"color":"red"}`,
|
||||
isStatic: false
|
||||
}
|
||||
])
|
||||
test('should transform into directive node', () => {
|
||||
const { node } = transformWithStyleTransform(`<div style="color: red"/>`)
|
||||
expect(node.props[0]).toMatchObject({
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name: `bind`,
|
||||
@@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
|
||||
},
|
||||
exp: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_1`,
|
||||
content: `{"color":"red"}`,
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
@@ -60,7 +51,7 @@ describe('compiler: style transform', () => {
|
||||
bind: transformBind
|
||||
}
|
||||
})
|
||||
expect((node.codegenNode as CallExpression).arguments[1]).toMatchObject({
|
||||
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||
properties: [
|
||||
{
|
||||
@@ -71,13 +62,13 @@ describe('compiler: style transform', () => {
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_1`,
|
||||
content: `{"color":"red"}`,
|
||||
isStatic: false
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
// should not cause the STYLE patchFlag to be attached
|
||||
expect((node.codegenNode as CallExpression).arguments.length).toBe(2)
|
||||
expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
parse,
|
||||
transform,
|
||||
ElementNode,
|
||||
CallExpression
|
||||
} from '@vue/compiler-core'
|
||||
import { transformCloak } from '../../src/transforms/vCloak'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
|
||||
function transformWithCloak(template: string) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
directiveTransforms: {
|
||||
cloak: transformCloak
|
||||
}
|
||||
})
|
||||
return ast.children[0] as ElementNode
|
||||
}
|
||||
|
||||
describe('compiler: v-cloak transform', () => {
|
||||
test('should add no props to DOM', () => {
|
||||
const node = transformWithCloak(`<div v-cloak/>`)
|
||||
const codegenArgs = (node.codegenNode as CallExpression).arguments
|
||||
|
||||
// As v-cloak 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(codegenArgs.length).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
PlainElementNode,
|
||||
CompilerOptions
|
||||
@@ -29,15 +29,13 @@ describe('compiler: v-html transform', () => {
|
||||
it('should convert v-html to innerHTML', () => {
|
||||
const ast = transformWithVHtml(`<div v-html="test"/>`)
|
||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||
arguments: [
|
||||
`"div"`,
|
||||
createObjectMatcher({
|
||||
innerHTML: `[test]`
|
||||
}),
|
||||
`null`,
|
||||
genFlagText(PatchFlags.PROPS),
|
||||
`["innerHTML"]`
|
||||
]
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
innerHTML: `[test]`
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
dynamicProps: `["innerHTML"]`
|
||||
})
|
||||
})
|
||||
|
||||
@@ -50,15 +48,13 @@ describe('compiler: v-html transform', () => {
|
||||
[{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
|
||||
])
|
||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||
arguments: [
|
||||
`"div"`,
|
||||
createObjectMatcher({
|
||||
innerHTML: `[test]`
|
||||
}),
|
||||
`null`, // <-- children should have been removed
|
||||
genFlagText(PatchFlags.PROPS),
|
||||
`["innerHTML"]`
|
||||
]
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
innerHTML: `[test]`
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
dynamicProps: `["innerHTML"]`
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { parse, transform, CompilerOptions, generate } from '@vue/compiler-core'
|
||||
import {
|
||||
baseParse as parse,
|
||||
transform,
|
||||
CompilerOptions,
|
||||
generate
|
||||
} from '@vue/compiler-core'
|
||||
import { transformModel } from '../../src/transforms/vModel'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
@@ -58,6 +63,19 @@ describe('compiler: transform v-model', () => {
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('input w/ dynamic v-bind', () => {
|
||||
const root = transformWithModel('<input v-bind="obj" v-model="model" />')
|
||||
|
||||
expect(root.helpers).toContain(V_MODEL_DYNAMIC)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
|
||||
const root2 = transformWithModel(
|
||||
'<input v-bind:[key]="val" v-model="model" />'
|
||||
)
|
||||
expect(root2.helpers).toContain(V_MODEL_DYNAMIC)
|
||||
expect(generate(root2).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('simple expression for select', () => {
|
||||
const root = transformWithModel('<select v-model="model" />')
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import {
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
CompilerOptions,
|
||||
ElementNode,
|
||||
ObjectExpression,
|
||||
CallExpression,
|
||||
NodeTypes
|
||||
NodeTypes,
|
||||
VNodeCall
|
||||
} from '@vue/compiler-core'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
import {
|
||||
createObjectMatcher,
|
||||
genFlagText
|
||||
} from '../../../compiler-core/__tests__/testUtils'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
@@ -24,8 +28,8 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
})
|
||||
return {
|
||||
root: ast,
|
||||
props: (((ast.children[0] as ElementNode).codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
props: (((ast.children[0] as ElementNode).codegenNode as VNodeCall)
|
||||
.props as ObjectExpression).properties
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +152,58 @@ describe('compiler-dom: transform v-on', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('should transform click.right', () => {
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @click.right="test"/>`)
|
||||
expect(prop.key).toMatchObject({
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `onContextmenu`
|
||||
})
|
||||
|
||||
// dynamic
|
||||
const {
|
||||
props: [prop2]
|
||||
} = parseWithVOn(`<div @[event].right="test"/>`)
|
||||
// ("on" + (event)).toLowerCase() === "onclick" ? "onContextmenu" : ("on" + (event))
|
||||
expect(prop2.key).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
`(`,
|
||||
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||
`).toLowerCase() === "onclick" ? "onContextmenu" : (`,
|
||||
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||
`)`
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('should transform click.middle', () => {
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @click.middle="test"/>`)
|
||||
expect(prop.key).toMatchObject({
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `onMouseup`
|
||||
})
|
||||
|
||||
// dynamic
|
||||
const {
|
||||
props: [prop2]
|
||||
} = parseWithVOn(`<div @[event].middle="test"/>`)
|
||||
// ("on" + (event)).toLowerCase() === "onclick" ? "onMouseup" : ("on" + (event))
|
||||
expect(prop2.key).toMatchObject({
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
`(`,
|
||||
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||
`).toLowerCase() === "onclick" ? "onMouseup" : (`,
|
||||
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||
`)`
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('cache handler w/ modifiers', () => {
|
||||
const {
|
||||
root,
|
||||
@@ -157,8 +213,11 @@ describe('compiler-dom: transform v-on', () => {
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect((root as any).children[0].codegenNode.arguments.length).toBe(2)
|
||||
// should not treat cached handler as dynamicProp, so it should have no
|
||||
// dynamicProps flags and only the hydration flag
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
||||
)
|
||||
expect(prop.value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { parse, transform, generate, CompilerOptions } from '@vue/compiler-core'
|
||||
import {
|
||||
baseParse as parse,
|
||||
transform,
|
||||
generate,
|
||||
CompilerOptions
|
||||
} from '@vue/compiler-core'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
import { transformShow } from '../../src/transforms/vShow'
|
||||
import { DOMErrorCodes } from '../../src/errors'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
PlainElementNode,
|
||||
CompilerOptions
|
||||
@@ -29,15 +29,13 @@ describe('compiler: v-text transform', () => {
|
||||
it('should convert v-text to textContent', () => {
|
||||
const ast = transformWithVText(`<div v-text="test"/>`)
|
||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||
arguments: [
|
||||
`"div"`,
|
||||
createObjectMatcher({
|
||||
textContent: `[test]`
|
||||
}),
|
||||
`null`,
|
||||
genFlagText(PatchFlags.PROPS),
|
||||
`["textContent"]`
|
||||
]
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
textContent: `[test]`
|
||||
}),
|
||||
children: undefined,
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
dynamicProps: `["textContent"]`
|
||||
})
|
||||
})
|
||||
|
||||
@@ -50,15 +48,13 @@ describe('compiler: v-text transform', () => {
|
||||
[{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
|
||||
])
|
||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||
arguments: [
|
||||
`"div"`,
|
||||
createObjectMatcher({
|
||||
textContent: `[test]`
|
||||
}),
|
||||
`null`, // <-- children should have been removed
|
||||
genFlagText(PatchFlags.PROPS),
|
||||
`["textContent"]`
|
||||
]
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
textContent: `[test]`
|
||||
}),
|
||||
children: undefined, // <-- children should have been removed
|
||||
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||
dynamicProps: `["textContent"]`
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { compile } from '../../src'
|
||||
|
||||
describe('compiler warnings', () => {
|
||||
describe('Transition', () => {
|
||||
function checkWarning(
|
||||
template: string,
|
||||
shouldWarn: boolean,
|
||||
message = `<Transition> expects exactly one child element or component.`
|
||||
) {
|
||||
const spy = jest.fn()
|
||||
compile(template.trim(), {
|
||||
hoistStatic: true,
|
||||
transformHoist: null,
|
||||
onError: err => {
|
||||
spy(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
|
||||
else expect(spy).not.toHaveBeenCalled()
|
||||
}
|
||||
|
||||
test('warns if multiple children', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div>hey</div>
|
||||
<div>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with v-for', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-for="i in items">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with multiple v-if + v-for', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a" v-for="i in items">hey</div>
|
||||
<div v-else v-for="i in items">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with template v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<template v-if="ok"></template>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with multiple templates', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<template v-if="a"></template>
|
||||
<template v-else></template>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns if multiple children with v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="one">hey</div>
|
||||
<div v-if="other">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with regular element', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with one single v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with v-if v-else-if v-else', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
<div v-else-if="b">hey</div>
|
||||
<div v-else>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with v-if v-else', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
<div v-else>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
"types": "dist/compiler-dom.d.ts",
|
||||
"unpkg": "dist/compiler-dom/global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"types": "dist/compiler-dom.d.ts",
|
||||
"unpkg": "dist/compiler-dom/global.js",
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"name": "VueDOMCompiler",
|
||||
"name": "VueCompilerDOM",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vue.git"
|
||||
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
@@ -30,10 +30,11 @@
|
||||
"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-dom#readme",
|
||||
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.0.0-alpha.0"
|
||||
"@vue/shared": "3.0.0-alpha.11",
|
||||
"@vue/compiler-core": "3.0.0-alpha.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ export const enum DOMErrorCodes {
|
||||
X_V_MODEL_ON_INVALID_ELEMENT,
|
||||
X_V_MODEL_ARG_ON_ELEMENT,
|
||||
X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
||||
X_V_SHOW_NO_EXPRESSION
|
||||
X_V_MODEL_UNNECESSARY_VALUE,
|
||||
X_V_SHOW_NO_EXPRESSION,
|
||||
X_TRANSITION_INVALID_CHILDREN,
|
||||
__EXTEND_POINT__
|
||||
}
|
||||
|
||||
export const DOMErrorMessages: { [code: number]: string } = {
|
||||
@@ -39,5 +42,7 @@ export const DOMErrorMessages: { [code: number]: string } = {
|
||||
[DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
|
||||
[DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT]: `v-model argument is not supported on plain elements.`,
|
||||
[DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.`,
|
||||
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`
|
||||
[DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
|
||||
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
|
||||
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`
|
||||
}
|
||||
|
||||
@@ -1,45 +1,67 @@
|
||||
import {
|
||||
baseCompile,
|
||||
baseParse,
|
||||
CompilerOptions,
|
||||
CodegenResult,
|
||||
isBuiltInType
|
||||
ParserOptions,
|
||||
RootNode,
|
||||
noopDirectiveTransform,
|
||||
NodeTransform,
|
||||
DirectiveTransform
|
||||
} from '@vue/compiler-core'
|
||||
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
||||
import { parserOptionsStandard } from './parserOptionsStandard'
|
||||
import { transformStyle } from './transforms/transformStyle'
|
||||
import { transformCloak } from './transforms/vCloak'
|
||||
import { transformVHtml } from './transforms/vHtml'
|
||||
import { transformVText } from './transforms/vText'
|
||||
import { transformModel } from './transforms/vModel'
|
||||
import { transformOn } from './transforms/vOn'
|
||||
import { transformShow } from './transforms/vShow'
|
||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
|
||||
import { stringifyStatic } from './transforms/stringifyStatic'
|
||||
|
||||
export const parserOptions = __BROWSER__
|
||||
? parserOptionsMinimal
|
||||
: parserOptionsStandard
|
||||
|
||||
export const DOMNodeTransforms: NodeTransform[] = [
|
||||
transformStyle,
|
||||
...(__DEV__ ? [warnTransitionChildren] : [])
|
||||
]
|
||||
|
||||
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
|
||||
cloak: noopDirectiveTransform,
|
||||
html: transformVHtml,
|
||||
text: transformVText,
|
||||
model: transformModel, // override compiler-core
|
||||
on: transformOn, // override compiler-core
|
||||
show: transformShow
|
||||
}
|
||||
|
||||
export function compile(
|
||||
template: string,
|
||||
options: CompilerOptions = {}
|
||||
): CodegenResult {
|
||||
return baseCompile(template, {
|
||||
...parserOptions,
|
||||
...options,
|
||||
...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
|
||||
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
|
||||
nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
|
||||
directiveTransforms: {
|
||||
cloak: transformCloak,
|
||||
html: transformVHtml,
|
||||
text: transformVText,
|
||||
model: transformModel, // override compiler-core
|
||||
on: transformOn,
|
||||
show: transformShow,
|
||||
...DOMDirectiveTransforms,
|
||||
...(options.directiveTransforms || {})
|
||||
},
|
||||
isBuiltInComponent: tag => {
|
||||
if (isBuiltInType(tag, `Transition`)) {
|
||||
return TRANSITION
|
||||
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
||||
return TRANSITION_GROUP
|
||||
}
|
||||
}
|
||||
transformHoist: __BROWSER__ ? null : stringifyStatic
|
||||
})
|
||||
}
|
||||
|
||||
export function parse(template: string, options: ParserOptions = {}): RootNode {
|
||||
return baseParse(template, {
|
||||
...parserOptions,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
export * from './runtimeHelpers'
|
||||
export { transformStyle } from './transforms/transformStyle'
|
||||
export { createDOMCompilerError, DOMErrorCodes } from './errors'
|
||||
export * from '@vue/compiler-core'
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
ParserOptions,
|
||||
ElementNode,
|
||||
Namespaces,
|
||||
NodeTypes
|
||||
NodeTypes,
|
||||
isBuiltInType
|
||||
} from '@vue/compiler-core'
|
||||
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||
|
||||
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
||||
'style,iframe,script,noscript',
|
||||
@@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
|
||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||
isPreTag: tag => tag === 'pre',
|
||||
|
||||
isBuiltInComponent: (tag: string): symbol | undefined => {
|
||||
if (isBuiltInType(tag, `Transition`)) {
|
||||
return TRANSITION
|
||||
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
||||
return TRANSITION_GROUP
|
||||
}
|
||||
},
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
||||
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
||||
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
||||
|
||||
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
NodeTypes,
|
||||
ElementNode,
|
||||
TransformContext,
|
||||
TemplateChildNode,
|
||||
SimpleExpressionNode,
|
||||
createCallExpression,
|
||||
HoistTransform,
|
||||
CREATE_STATIC,
|
||||
ExpressionNode
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
isVoidTag,
|
||||
isString,
|
||||
isSymbol,
|
||||
escapeHtml,
|
||||
toDisplayString,
|
||||
normalizeClass,
|
||||
normalizeStyle,
|
||||
stringifyStyle
|
||||
} from '@vue/shared'
|
||||
|
||||
// Turn eligible hoisted static trees into stringied static nodes, e.g.
|
||||
// const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
|
||||
// This is only performed in non-in-browser compilations.
|
||||
export const stringifyStatic: HoistTransform = (node, context) => {
|
||||
if (shouldOptimize(node)) {
|
||||
return createCallExpression(context.helper(CREATE_STATIC), [
|
||||
JSON.stringify(stringifyElement(node, context))
|
||||
])
|
||||
} else {
|
||||
return node.codegenNode!
|
||||
}
|
||||
}
|
||||
|
||||
export const enum StringifyThresholds {
|
||||
ELEMENT_WITH_BINDING_COUNT = 5,
|
||||
NODE_COUNT = 20
|
||||
}
|
||||
|
||||
// Opt-in heuristics based on:
|
||||
// 1. number of elements with attributes > 5.
|
||||
// 2. OR: number of total nodes > 20
|
||||
// For some simple trees, the performance can actually be worse.
|
||||
// it is only worth it when the tree is complex enough
|
||||
// (e.g. big piece of static content)
|
||||
function shouldOptimize(node: ElementNode): boolean {
|
||||
let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
let nodeThreshold = StringifyThresholds.NODE_COUNT
|
||||
|
||||
// TODO: check for cases where using innerHTML will result in different
|
||||
// output compared to imperative node insertions.
|
||||
// probably only need to check for most common case
|
||||
// i.e. non-phrasing-content tags inside `<p>`
|
||||
function walk(node: ElementNode) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
if (--nodeThreshold === 0) {
|
||||
return true
|
||||
}
|
||||
const child = node.children[i]
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
if (child.props.length > 0 && --bindingThreshold === 0) {
|
||||
return true
|
||||
}
|
||||
if (walk(child)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return walk(node)
|
||||
}
|
||||
|
||||
function stringifyElement(
|
||||
node: ElementNode,
|
||||
context: TransformContext
|
||||
): string {
|
||||
let res = `<${node.tag}`
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i]
|
||||
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||
res += ` ${p.name}`
|
||||
if (p.value) {
|
||||
res += `="${escapeHtml(p.value.content)}"`
|
||||
}
|
||||
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
|
||||
// constant v-bind, e.g. :foo="1"
|
||||
let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
|
||||
const arg = p.arg && (p.arg as SimpleExpressionNode).content
|
||||
if (arg === 'class') {
|
||||
evaluated = normalizeClass(evaluated)
|
||||
} else if (arg === 'style') {
|
||||
evaluated = stringifyStyle(normalizeStyle(evaluated))
|
||||
}
|
||||
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
|
||||
evaluated
|
||||
)}"`
|
||||
}
|
||||
}
|
||||
if (context.scopeId) {
|
||||
res += ` ${context.scopeId}`
|
||||
}
|
||||
res += `>`
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
res += stringifyNode(node.children[i], context)
|
||||
}
|
||||
if (!isVoidTag(node.tag)) {
|
||||
res += `</${node.tag}>`
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function stringifyNode(
|
||||
node: string | TemplateChildNode,
|
||||
context: TransformContext
|
||||
): string {
|
||||
if (isString(node)) {
|
||||
return node
|
||||
}
|
||||
if (isSymbol(node)) {
|
||||
return ``
|
||||
}
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT:
|
||||
return stringifyElement(node, context)
|
||||
case NodeTypes.TEXT:
|
||||
return escapeHtml(node.content)
|
||||
case NodeTypes.COMMENT:
|
||||
return `<!--${escapeHtml(node.content)}-->`
|
||||
case NodeTypes.INTERPOLATION:
|
||||
return escapeHtml(toDisplayString(evaluateConstant(node.content)))
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
return escapeHtml(evaluateConstant(node))
|
||||
case NodeTypes.TEXT_CALL:
|
||||
return stringifyNode(node.content, context)
|
||||
default:
|
||||
// static trees will not contain if/for nodes
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// __UNSAFE__
|
||||
// Reason: eval.
|
||||
// It's technically safe to eval because only constant expressions are possible
|
||||
// here, e.g. `{{ 1 }}` or `{{ 'foo' }}`
|
||||
// in addition, constant exps bail on presence of parens so you can't even
|
||||
// run JSFuck in here. But we mark it unsafe for security review purposes.
|
||||
// (see compiler-core/src/transformExpressions)
|
||||
function evaluateConstant(exp: ExpressionNode): string {
|
||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
return new Function(`return ${exp.content}`)()
|
||||
} else {
|
||||
// compound
|
||||
let res = ``
|
||||
exp.children.forEach(c => {
|
||||
if (isString(c) || isSymbol(c)) {
|
||||
return
|
||||
}
|
||||
if (c.type === NodeTypes.TEXT) {
|
||||
res += c.content
|
||||
} else if (c.type === NodeTypes.INTERPOLATION) {
|
||||
res += toDisplayString(evaluateConstant(c.content))
|
||||
} else {
|
||||
res += evaluateConstant(c)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
NodeTransform,
|
||||
NodeTypes,
|
||||
createSimpleExpression
|
||||
createSimpleExpression,
|
||||
SimpleExpressionNode,
|
||||
SourceLocation
|
||||
} from '@vue/compiler-core'
|
||||
|
||||
// Parse inline CSS strings for static style attributes into an object.
|
||||
@@ -15,13 +17,11 @@ export const transformStyle: NodeTransform = (node, context) => {
|
||||
node.props.forEach((p, i) => {
|
||||
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
||||
// replace p with an expression node
|
||||
const parsed = JSON.stringify(parseInlineCSS(p.value.content))
|
||||
const exp = context.hoist(createSimpleExpression(parsed, false, p.loc))
|
||||
node.props[i] = {
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name: `bind`,
|
||||
arg: createSimpleExpression(`style`, true, p.loc),
|
||||
exp,
|
||||
exp: parseInlineCSS(p.value.content, p.loc),
|
||||
modifiers: [],
|
||||
loc: p.loc
|
||||
}
|
||||
@@ -33,7 +33,10 @@ export const transformStyle: NodeTransform = (node, context) => {
|
||||
const listDelimiterRE = /;(?![^(]*\))/g
|
||||
const propertyDelimiterRE = /:(.+)/
|
||||
|
||||
function parseInlineCSS(cssText: string): Record<string, string> {
|
||||
function parseInlineCSS(
|
||||
cssText: string,
|
||||
loc: SourceLocation
|
||||
): SimpleExpressionNode {
|
||||
const res: Record<string, string> = {}
|
||||
cssText.split(listDelimiterRE).forEach(item => {
|
||||
if (item) {
|
||||
@@ -41,5 +44,5 @@ function parseInlineCSS(cssText: string): Record<string, string> {
|
||||
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
|
||||
}
|
||||
})
|
||||
return res
|
||||
return createSimpleExpression(JSON.stringify(res), false, loc, true)
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { DirectiveTransform } from 'packages/compiler-core/src/transform'
|
||||
|
||||
export const transformCloak: DirectiveTransform = (node, context) => {
|
||||
return { props: [], needRuntime: false }
|
||||
}
|
||||
@@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`innerHTML`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import {
|
||||
DirectiveTransform,
|
||||
ElementTypes,
|
||||
findProp,
|
||||
NodeTypes
|
||||
NodeTypes,
|
||||
hasDynamicKeyVBind
|
||||
} from '@vue/compiler-core'
|
||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||
import {
|
||||
@@ -16,68 +17,104 @@ import {
|
||||
|
||||
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
const baseResult = baseTransform(dir, node, context)
|
||||
// base transform has errors
|
||||
if (!baseResult.props.length) {
|
||||
// base transform has errors OR component v-model (only need props)
|
||||
if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
|
||||
return baseResult
|
||||
}
|
||||
|
||||
const { tag, tagType } = node
|
||||
if (tagType === ElementTypes.ELEMENT) {
|
||||
if (dir.arg) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
|
||||
dir.arg.loc
|
||||
)
|
||||
if (dir.arg) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
|
||||
dir.arg.loc
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
||||
let directiveToUse = V_MODEL_TEXT
|
||||
let isInvalidType = false
|
||||
if (tag === 'input') {
|
||||
const type = findProp(node, `type`)
|
||||
if (type) {
|
||||
if (type.type === NodeTypes.DIRECTIVE) {
|
||||
// :type="foo"
|
||||
directiveToUse = V_MODEL_DYNAMIC
|
||||
} else if (type.value) {
|
||||
switch (type.value.content) {
|
||||
case 'radio':
|
||||
directiveToUse = V_MODEL_RADIO
|
||||
break
|
||||
case 'checkbox':
|
||||
directiveToUse = V_MODEL_CHECKBOX
|
||||
break
|
||||
case 'file':
|
||||
isInvalidType = true
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
||||
dir.loc
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (tag === 'select') {
|
||||
directiveToUse = V_MODEL_SELECT
|
||||
}
|
||||
// inject runtime directive
|
||||
// by returning the helper symbol via needRuntime
|
||||
// the import will replaced a resolveDirective call.
|
||||
if (!isInvalidType) {
|
||||
baseResult.needRuntime = context.helper(directiveToUse)
|
||||
}
|
||||
} else {
|
||||
function checkDuplicatedValue() {
|
||||
const value = findProp(node, 'value')
|
||||
if (value) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
|
||||
dir.loc
|
||||
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
||||
value.loc
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const { tag } = node
|
||||
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
||||
let directiveToUse = V_MODEL_TEXT
|
||||
let isInvalidType = false
|
||||
if (tag === 'input') {
|
||||
const type = findProp(node, `type`)
|
||||
if (type) {
|
||||
if (type.type === NodeTypes.DIRECTIVE) {
|
||||
// :type="foo"
|
||||
directiveToUse = V_MODEL_DYNAMIC
|
||||
} else if (type.value) {
|
||||
switch (type.value.content) {
|
||||
case 'radio':
|
||||
directiveToUse = V_MODEL_RADIO
|
||||
break
|
||||
case 'checkbox':
|
||||
directiveToUse = V_MODEL_CHECKBOX
|
||||
break
|
||||
case 'file':
|
||||
isInvalidType = true
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
||||
dir.loc
|
||||
)
|
||||
)
|
||||
break
|
||||
default:
|
||||
// text type
|
||||
__DEV__ && checkDuplicatedValue()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (hasDynamicKeyVBind(node)) {
|
||||
// element has bindings with dynamic keys, which can possibly contain
|
||||
// "type".
|
||||
directiveToUse = V_MODEL_DYNAMIC
|
||||
} else {
|
||||
// text type
|
||||
__DEV__ && checkDuplicatedValue()
|
||||
}
|
||||
} else if (tag === 'select') {
|
||||
directiveToUse = V_MODEL_SELECT
|
||||
} else if (tag === 'textarea') {
|
||||
__DEV__ && checkDuplicatedValue()
|
||||
}
|
||||
// inject runtime directive
|
||||
// by returning the helper symbol via needRuntime
|
||||
// the import will replaced a resolveDirective call.
|
||||
if (!isInvalidType) {
|
||||
baseResult.needRuntime = context.helper(directiveToUse)
|
||||
}
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
|
||||
dir.loc
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// native vmodel doesn't need the `modelValue` props since they are also
|
||||
// passed to the runtime as `binding.value`. removing it reduces code size.
|
||||
baseResult.props = baseResult.props.filter(p => {
|
||||
if (
|
||||
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||
p.key.content === 'modelValue'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return baseResult
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
createCallExpression,
|
||||
createObjectExpression,
|
||||
createSimpleExpression,
|
||||
NodeTypes
|
||||
NodeTypes,
|
||||
createCompoundExpression,
|
||||
ExpressionNode
|
||||
} from '@vue/compiler-core'
|
||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
|
||||
import { makeMap } from '@vue/shared'
|
||||
@@ -52,6 +54,24 @@ const generateModifiers = (modifiers: string[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const transformClick = (key: ExpressionNode, event: string) => {
|
||||
const isStaticClick =
|
||||
key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||
key.isStatic &&
|
||||
key.content.toLowerCase() === 'onclick'
|
||||
return isStaticClick
|
||||
? createSimpleExpression(event, true)
|
||||
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
||||
? createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`).toLowerCase() === "onclick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
])
|
||||
: key
|
||||
}
|
||||
|
||||
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
return baseTransform(dir, node, context, baseResult => {
|
||||
const { modifiers } = dir
|
||||
@@ -64,6 +84,14 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
eventOptionModifiers
|
||||
} = generateModifiers(modifiers)
|
||||
|
||||
// normalize click.right and click.middle since they don't actually fire
|
||||
if (nonKeyModifiers.includes('right')) {
|
||||
key = transformClick(key, `onContextmenu`)
|
||||
}
|
||||
if (nonKeyModifiers.includes('middle')) {
|
||||
key = transformClick(key, `onMouseup`)
|
||||
}
|
||||
|
||||
if (nonKeyModifiers.length) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
||||
handlerExp,
|
||||
@@ -102,8 +130,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
}
|
||||
|
||||
return {
|
||||
props: [createObjectProperty(key, handlerExp)],
|
||||
needRuntime: false
|
||||
props: [createObjectProperty(key, handlerExp)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`textContent`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
NodeTransform,
|
||||
NodeTypes,
|
||||
ElementTypes,
|
||||
ComponentNode,
|
||||
IfBranchNode
|
||||
} from '@vue/compiler-core'
|
||||
import { TRANSITION } from '../runtimeHelpers'
|
||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||
|
||||
export const warnTransitionChildren: NodeTransform = (node, context) => {
|
||||
if (
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.COMPONENT
|
||||
) {
|
||||
const component = context.isBuiltInComponent(node.tag)
|
||||
if (component === TRANSITION) {
|
||||
return () => {
|
||||
if (node.children.length && hasMultipleChildren(node)) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN,
|
||||
{
|
||||
start: node.children[0].loc.start,
|
||||
end: node.children[node.children.length - 1].loc.end,
|
||||
source: ''
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
|
||||
const child = node.children[0]
|
||||
return (
|
||||
node.children.length !== 1 ||
|
||||
child.type === NodeTypes.FOR ||
|
||||
(child.type === NodeTypes.IF && child.branches.some(hasMultipleChildren))
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`source map 1`] = `
|
||||
Object {
|
||||
"mappings": ";;;;UAAA,aACE;IAAK,gCAAMA,WAAM",
|
||||
"mappings": ";;;wBACE,aAA8B;IAAzB,aAAmB,4BAAbA,WAAM",
|
||||
"names": Array [
|
||||
"render",
|
||||
],
|
||||
@@ -20,8 +20,8 @@ Object {
|
||||
|
||||
exports[`template errors 1`] = `
|
||||
Array [
|
||||
[SyntaxError: Invalid JavaScript expression. (2:13)],
|
||||
[SyntaxError: v-bind is missing expression. (1:6)],
|
||||
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements. (2:17)],
|
||||
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
||||
[SyntaxError: v-bind is missing expression.],
|
||||
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler sfc: transform asset url support uri fragment 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||
import _imports_0 from '@svg/file.svg'
|
||||
|
||||
|
||||
const _hoisted_1 = _imports_0 + '#fragment'
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"use\\", { href: _hoisted_1 }))
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(\\"use\\", { href: _hoisted_1 }))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform asset url support uri is empty 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(\\"use\\", { href: '' }))
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(\\"use\\", { href: '' }))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform asset url transform assetUrls 1`] = `
|
||||
"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
|
||||
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||
import _imports_0 from './logo.png'
|
||||
import _imports_1 from 'fixtures/logo.png'
|
||||
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(Fragment, null, [
|
||||
createVNode(\\"img\\", { src: _imports_0 }),
|
||||
createVNode(\\"img\\", { src: _imports_1 }),
|
||||
createVNode(\\"img\\", { src: _imports_1 })
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"img\\", { src: _imports_0 }),
|
||||
_createVNode(\\"img\\", { src: _imports_1 }),
|
||||
_createVNode(\\"img\\", { src: _imports_1 }),
|
||||
_createVNode(\\"img\\", { src: \\"http://example.com/fixtures/logo.png\\" }),
|
||||
_createVNode(\\"img\\", { src: \\"/fixtures/logo.png\\" })
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler sfc: transform srcset transform srcset 1`] = `
|
||||
"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
|
||||
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||
import _imports_0 from './logo.png'
|
||||
|
||||
|
||||
@@ -12,37 +12,49 @@ const _hoisted_4 = _imports_0 + ', ' + _imports_0 + '2x'
|
||||
const _hoisted_5 = _imports_0 + '2x, ' + _imports_0
|
||||
const _hoisted_6 = _imports_0 + '2x, ' + _imports_0 + '3x'
|
||||
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + '2x, ' + _imports_0 + '3x'
|
||||
const _hoisted_8 = \\"/logo.png\\" + ', ' + _imports_0 + '2x'
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(Fragment, null, [
|
||||
createVNode(\\"img\\", {
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_1
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_2
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_3
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_4
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_5
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_6
|
||||
}),
|
||||
createVNode(\\"img\\", {
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"./logo.png\\",
|
||||
srcset: _hoisted_7
|
||||
}),
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"/logo.png\\",
|
||||
srcset: \\"/logo.png, /logo.png 2x\\"
|
||||
}),
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"https://example.com/logo.png\\",
|
||||
srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\"
|
||||
}),
|
||||
_createVNode(\\"img\\", {
|
||||
src: \\"/logo.png\\",
|
||||
srcset: _hoisted_8
|
||||
})
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
|
||||
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { compileStyle } from '../src/compileStyle'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
function compile(source: string): string {
|
||||
const res = compileStyle({
|
||||
source,
|
||||
filename: 'test.css',
|
||||
id: 'test'
|
||||
})
|
||||
if (res.errors.length) {
|
||||
res.errors.forEach(err => {
|
||||
console.error(err)
|
||||
})
|
||||
expect(res.errors.length).toBe(0)
|
||||
}
|
||||
return res.code
|
||||
}
|
||||
|
||||
describe('SFC scoped CSS', () => {
|
||||
mockWarn()
|
||||
|
||||
test('simple selectors', () => {
|
||||
expect(compile(`h1 { color: red; }`)).toMatch(`h1[test] { color: red;`)
|
||||
expect(compile(`.foo { color: red; }`)).toMatch(`.foo[test] { color: red;`)
|
||||
})
|
||||
|
||||
test('descendent selector', () => {
|
||||
expect(compile(`h1 .foo { color: red; }`)).toMatch(
|
||||
`h1 .foo[test] { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('multiple selectors', () => {
|
||||
expect(compile(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
|
||||
`h1 .foo[test], .bar[test], .baz[test] { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('pseudo class', () => {
|
||||
expect(compile(`.foo:after { color: red; }`)).toMatch(
|
||||
`.foo[test]:after { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('pseudo element', () => {
|
||||
expect(compile(`::selection { display: none; }`)).toMatch(
|
||||
'[test]::selection {'
|
||||
)
|
||||
})
|
||||
|
||||
test('spaces before pseudo element', () => {
|
||||
const code = compile(`.abc, ::selection { color: red; }`)
|
||||
expect(code).toMatch('.abc[test],')
|
||||
expect(code).toMatch('[test]::selection {')
|
||||
})
|
||||
|
||||
test('::v-deep', () => {
|
||||
expect(compile(`::v-deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-deep(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"[test] .foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".baz .qux[test] .foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-slotted', () => {
|
||||
expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-slotted(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".baz .qux .foo .bar[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-global', () => {
|
||||
expect(compile(`::v-global(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-global(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
// global ignores anything before it
|
||||
expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('media query', () => {
|
||||
expect(compile(`@media print { .foo { color: red }}`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"@media print {
|
||||
.foo[test] { color: red
|
||||
}}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('supports query', () => {
|
||||
expect(compile(`@supports(display: grid) { .foo { display: grid }}`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"@supports(display: grid) {
|
||||
.foo[test] { display: grid
|
||||
}}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scoped keyframes', () => {
|
||||
const style = compile(`
|
||||
.anim {
|
||||
animation: color 5s infinite, other 5s;
|
||||
}
|
||||
.anim-2 {
|
||||
animation-name: color;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
.anim-3 {
|
||||
animation: 5s color infinite, 5s other;
|
||||
}
|
||||
.anim-multiple {
|
||||
animation: color 5s infinite, opacity 2s;
|
||||
}
|
||||
.anim-multiple-2 {
|
||||
animation-name: color, opacity;
|
||||
animation-duration: 5s, 2s;
|
||||
}
|
||||
|
||||
@keyframes color {
|
||||
from { color: red; }
|
||||
to { color: green; }
|
||||
}
|
||||
@-webkit-keyframes color {
|
||||
from { color: red; }
|
||||
to { color: green; }
|
||||
}
|
||||
@keyframes opacity {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-webkit-keyframes opacity {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
`)
|
||||
|
||||
expect(style).toContain(
|
||||
`.anim[test] {\n animation: color-test 5s infinite, other 5s;`
|
||||
)
|
||||
expect(style).toContain(`.anim-2[test] {\n animation-name: color-test`)
|
||||
expect(style).toContain(
|
||||
`.anim-3[test] {\n animation: 5s color-test infinite, 5s other;`
|
||||
)
|
||||
expect(style).toContain(`@keyframes color-test {`)
|
||||
expect(style).toContain(`@-webkit-keyframes color-test {`)
|
||||
|
||||
expect(style).toContain(
|
||||
`.anim-multiple[test] {\n animation: color-test 5s infinite,opacity-test 2s;`
|
||||
)
|
||||
expect(style).toContain(
|
||||
`.anim-multiple-2[test] {\n animation-name: color-test,opacity-test;`
|
||||
)
|
||||
expect(style).toContain(`@keyframes opacity-test {`)
|
||||
expect(style).toContain(`@-webkit-keyframes opacity-test {`)
|
||||
})
|
||||
|
||||
// vue-loader/#1370
|
||||
test('spaces after selector', () => {
|
||||
const { code } = compileStyle({
|
||||
source: `.foo , .bar { color: red; }`,
|
||||
filename: 'test.css',
|
||||
id: 'test'
|
||||
})
|
||||
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
".foo[test], .bar[test] { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('deprecated syntax', () => {
|
||||
test('::v-deep as combinator', () => {
|
||||
expect(compile(`::v-deep .foo { color: red; }`)).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.bar ::v-deep .foo { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".bar[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`::v-deep usage as a combinator has been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('>>> (deprecated syntax)', () => {
|
||||
const code = compile(`>>> .foo { color: red; }`)
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`the >>> and /deep/ combinators have been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('/deep/ (deprecated syntax)', () => {
|
||||
const code = compile(`/deep/ .foo { color: red; }`)
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`the >>> and /deep/ combinators have been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -9,7 +9,7 @@ test('should work', () => {
|
||||
expect(result.errors.length).toBe(0)
|
||||
expect(result.source).toBe(source)
|
||||
// should expose render fn
|
||||
expect(result.code).toMatch(`export default function render()`)
|
||||
expect(result.code).toMatch(`export function render(`)
|
||||
})
|
||||
|
||||
test('preprocess pug', () => {
|
||||
@@ -23,7 +23,7 @@ body
|
||||
</template>
|
||||
`,
|
||||
{ filename: 'example.vue', sourceMap: true }
|
||||
).template as SFCTemplateBlock
|
||||
).descriptor.template as SFCTemplateBlock
|
||||
|
||||
const result = compileTemplate({
|
||||
filename: 'example.vue',
|
||||
@@ -35,10 +35,10 @@ body
|
||||
})
|
||||
|
||||
test('warn missing preprocessor', () => {
|
||||
const template = parse(`<template lang="unknownLang">\n</template>\n`, {
|
||||
const template = parse(`<template lang="unknownLang">hi</template>\n`, {
|
||||
filename: 'example.vue',
|
||||
sourceMap: true
|
||||
}).template as SFCTemplateBlock
|
||||
}).descriptor.template as SFCTemplateBlock
|
||||
|
||||
const result = compileTemplate({
|
||||
filename: 'example.vue',
|
||||
@@ -50,7 +50,7 @@ test('warn missing preprocessor', () => {
|
||||
})
|
||||
|
||||
test('transform asset url options', () => {
|
||||
const input = { source: `<foo bar="baz"/>`, filename: 'example.vue' }
|
||||
const input = { source: `<foo bar="~baz"/>`, filename: 'example.vue' }
|
||||
// Object option
|
||||
const { code: code1 } = compileTemplate({
|
||||
...input,
|
||||
@@ -70,10 +70,10 @@ test('source map', () => {
|
||||
`
|
||||
<template>
|
||||
<div><p>{{ render }}</p></div>
|
||||
</template>
|
||||
</template>
|
||||
`,
|
||||
{ filename: 'example.vue', sourceMap: true }
|
||||
).template as SFCTemplateBlock
|
||||
).descriptor.template as SFCTemplateBlock
|
||||
|
||||
const result = compileTemplate({
|
||||
filename: 'example.vue',
|
||||
@@ -86,7 +86,7 @@ test('source map', () => {
|
||||
test('template errors', () => {
|
||||
const result = compileTemplate({
|
||||
filename: 'example.vue',
|
||||
source: `<div :foo
|
||||
source: `<div :foo
|
||||
:bar="a[" v-model="baz"/>`
|
||||
})
|
||||
expect(result.errors).toMatchSnapshot()
|
||||
@@ -100,7 +100,7 @@ test('preprocessor errors', () => {
|
||||
</template>
|
||||
`,
|
||||
{ filename: 'example.vue', sourceMap: true }
|
||||
).template as SFCTemplateBlock
|
||||
).descriptor.template as SFCTemplateBlock
|
||||
|
||||
const result = compileTemplate({
|
||||
filename: 'example.vue',
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import { parse } from '../src'
|
||||
import { mockWarn } from '@vue/runtime-test'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
import { baseParse, baseCompile } from '@vue/compiler-core'
|
||||
import { SourceMapConsumer } from 'source-map'
|
||||
|
||||
describe('compiler:sfc', () => {
|
||||
mockWarn()
|
||||
|
||||
describe('source map', () => {
|
||||
test('style block', () => {
|
||||
const style = parse(`<style>\n.color {\n color: red;\n }\n</style>\n`)
|
||||
.styles[0]
|
||||
// TODO need to actually test this with SourceMapConsumer
|
||||
// Padding determines how many blank lines will there be before the style block
|
||||
const padding = Math.round(Math.random() * 10)
|
||||
const style = parse(
|
||||
`${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
|
||||
).descriptor.styles[0]
|
||||
|
||||
expect(style.map).not.toBeUndefined()
|
||||
|
||||
const consumer = new SourceMapConsumer(style.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
})
|
||||
})
|
||||
|
||||
test('script block', () => {
|
||||
const script = parse(`<script>\nconsole.log(1)\n }\n</script>\n`).script
|
||||
// TODO need to actually test this with SourceMapConsumer
|
||||
// Padding determines how many blank lines will there be before the style block
|
||||
const padding = Math.round(Math.random() * 10)
|
||||
const script = parse(
|
||||
`${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`
|
||||
).descriptor.script
|
||||
|
||||
expect(script!.map).not.toBeUndefined()
|
||||
|
||||
const consumer = new SourceMapConsumer(script!.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,12 +49,12 @@ export default {}
|
||||
<style>
|
||||
h1 { color: red }
|
||||
</style>`
|
||||
const padFalse = parse(content.trim(), { pad: false })
|
||||
const padFalse = parse(content.trim(), { pad: false }).descriptor
|
||||
expect(padFalse.template!.content).toBe('\n<div></div>\n')
|
||||
expect(padFalse.script!.content).toBe('\nexport default {}\n')
|
||||
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
|
||||
|
||||
const padTrue = parse(content.trim(), { pad: true })
|
||||
const padTrue = parse(content.trim(), { pad: true }).descriptor
|
||||
expect(padTrue.script!.content).toBe(
|
||||
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
||||
)
|
||||
@@ -43,7 +62,7 @@ h1 { color: red }
|
||||
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
||||
)
|
||||
|
||||
const padLine = parse(content.trim(), { pad: 'line' })
|
||||
const padLine = parse(content.trim(), { pad: 'line' }).descriptor
|
||||
expect(padLine.script!.content).toBe(
|
||||
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
||||
)
|
||||
@@ -51,7 +70,7 @@ h1 { color: red }
|
||||
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
||||
)
|
||||
|
||||
const padSpace = parse(content.trim(), { pad: 'space' })
|
||||
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
|
||||
expect(padSpace.script!.content).toBe(
|
||||
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
|
||||
'\nexport default {}\n'
|
||||
@@ -65,13 +84,55 @@ h1 { color: red }
|
||||
})
|
||||
|
||||
test('should ignore nodes with no content', () => {
|
||||
expect(parse(`<template/>`).template).toBe(null)
|
||||
expect(parse(`<script/>`).script).toBe(null)
|
||||
expect(parse(`<style/>`).styles.length).toBe(0)
|
||||
expect(parse(`<custom/>`).customBlocks.length).toBe(0)
|
||||
expect(parse(`<template/>`).descriptor.template).toBe(null)
|
||||
expect(parse(`<script/>`).descriptor.script).toBe(null)
|
||||
expect(parse(`<style/>`).descriptor.styles.length).toBe(0)
|
||||
expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
|
||||
})
|
||||
|
||||
describe('error', () => {
|
||||
test('handle empty nodes with src attribute', () => {
|
||||
const { descriptor } = parse(`<script src="com"/>`)
|
||||
expect(descriptor.script).toBeTruthy()
|
||||
expect(descriptor.script!.content).toBeFalsy()
|
||||
expect(descriptor.script!.attrs['src']).toBe('com')
|
||||
})
|
||||
|
||||
test('nested templates', () => {
|
||||
const content = `
|
||||
<template v-if="ok">ok</template>
|
||||
<div><div></div></div>
|
||||
`
|
||||
const { descriptor } = parse(`<template>${content}</template>`)
|
||||
expect(descriptor.template!.content).toBe(content)
|
||||
})
|
||||
|
||||
test('error tolerance', () => {
|
||||
const { errors } = parse(`<template>`)
|
||||
expect(errors.length).toBe(1)
|
||||
})
|
||||
|
||||
test('should parse as DOM by default', () => {
|
||||
const { errors } = parse(`<template><input></template>`)
|
||||
expect(errors.length).toBe(0)
|
||||
})
|
||||
|
||||
test('custom compiler', () => {
|
||||
const { errors } = parse(`<template><input></template>`, {
|
||||
compiler: {
|
||||
parse: baseParse,
|
||||
compile: baseCompile
|
||||
}
|
||||
})
|
||||
expect(errors.length).toBe(1)
|
||||
})
|
||||
|
||||
test('treat custom blocks as raw text', () => {
|
||||
const { errors, descriptor } = parse(`<foo> <-& </foo>`)
|
||||
expect(errors.length).toBe(0)
|
||||
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
|
||||
})
|
||||
|
||||
describe('warnings', () => {
|
||||
test('should only allow single template element', () => {
|
||||
parse(`<template><div/></template><template><div/></template>`)
|
||||
expect(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { generate, parse, transform } from '@vue/compiler-core'
|
||||
import { generate, baseParse, transform } from '@vue/compiler-core'
|
||||
import { transformAssetUrl } from '../src/templateTransformAssetUrl'
|
||||
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||
|
||||
function compileWithAssetUrls(template: string) {
|
||||
const ast = parse(template)
|
||||
const ast = baseParse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformAssetUrl, transformElement],
|
||||
directiveTransforms: {
|
||||
@@ -20,6 +20,8 @@ describe('compiler sfc: transform asset url', () => {
|
||||
<img src="./logo.png"/>
|
||||
<img src="~fixtures/logo.png"/>
|
||||
<img src="~/fixtures/logo.png"/>
|
||||
<img src="http://example.com/fixtures/logo.png"/>
|
||||
<img src="/fixtures/logo.png"/>
|
||||
`)
|
||||
|
||||
expect(result.code).toMatchSnapshot()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { generate, parse, transform } from '@vue/compiler-core'
|
||||
import { generate, baseParse, transform } from '@vue/compiler-core'
|
||||
import { transformSrcset } from '../src/templateTransformSrcset'
|
||||
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||
|
||||
function compileWithSrcset(template: string) {
|
||||
const ast = parse(template)
|
||||
const ast = baseParse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformSrcset, transformElement],
|
||||
directiveTransforms: {
|
||||
@@ -24,6 +24,9 @@ describe('compiler sfc: transform srcset', () => {
|
||||
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
|
||||
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
|
||||
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
|
||||
<img src="/logo.png" srcset="/logo.png, /logo.png 2x"/>
|
||||
<img src="https://example.com/logo.png" srcset="https://example.com/logo.png, https://example.com/logo.png 2x"/>
|
||||
<img src="/logo.png" srcset="/logo.png, ./logo.png 2x"/>
|
||||
`)
|
||||
|
||||
expect(result.code).toMatchSnapshot()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"types": "dist/compiler-sfc.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"types": "dist/compiler-sfc.d.ts",
|
||||
"buildOptions": {
|
||||
"prod": false,
|
||||
"formats": [
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vue.git"
|
||||
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
@@ -23,19 +23,24 @@
|
||||
"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-next/tree/master/packages/compiler-sfc#readme",
|
||||
"peerDependencies": {
|
||||
"vue": "3.0.0-alpha.11"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.0.0-alpha.0",
|
||||
"@vue/compiler-dom": "3.0.0-alpha.0",
|
||||
"@vue/shared": "3.0.0-alpha.11",
|
||||
"@vue/compiler-core": "3.0.0-alpha.11",
|
||||
"@vue/compiler-dom": "3.0.0-alpha.11",
|
||||
"@vue/compiler-ssr": "3.0.0-alpha.11",
|
||||
"consolidate": "^0.15.1",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"postcss": "^7.0.21",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"source-map": "^0.7.3"
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/consolidate": "^0.14.0",
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from './stylePreprocessors'
|
||||
import { RawSourceMap } from 'source-map'
|
||||
|
||||
export interface StyleCompileOptions {
|
||||
export interface SFCStyleCompileOptions {
|
||||
source: string
|
||||
filename: string
|
||||
id: string
|
||||
@@ -23,11 +23,11 @@ export interface StyleCompileOptions {
|
||||
postcssPlugins?: any[]
|
||||
}
|
||||
|
||||
export interface AsyncStyleCompileOptions extends StyleCompileOptions {
|
||||
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
|
||||
isAsync?: boolean
|
||||
}
|
||||
|
||||
export interface StyleCompileResults {
|
||||
export interface SFCStyleCompileResults {
|
||||
code: string
|
||||
map: RawSourceMap | undefined
|
||||
rawResult: LazyResult | Result | undefined
|
||||
@@ -35,22 +35,25 @@ export interface StyleCompileResults {
|
||||
}
|
||||
|
||||
export function compileStyle(
|
||||
options: StyleCompileOptions
|
||||
): StyleCompileResults {
|
||||
return doCompileStyle({ ...options, isAsync: false }) as StyleCompileResults
|
||||
options: SFCStyleCompileOptions
|
||||
): SFCStyleCompileResults {
|
||||
return doCompileStyle({
|
||||
...options,
|
||||
isAsync: false
|
||||
}) as SFCStyleCompileResults
|
||||
}
|
||||
|
||||
export function compileStyleAsync(
|
||||
options: StyleCompileOptions
|
||||
): Promise<StyleCompileResults> {
|
||||
options: SFCStyleCompileOptions
|
||||
): Promise<SFCStyleCompileResults> {
|
||||
return doCompileStyle({ ...options, isAsync: true }) as Promise<
|
||||
StyleCompileResults
|
||||
SFCStyleCompileResults
|
||||
>
|
||||
}
|
||||
|
||||
export function doCompileStyle(
|
||||
options: AsyncStyleCompileOptions
|
||||
): StyleCompileResults | Promise<StyleCompileResults> {
|
||||
options: SFCAsyncStyleCompileOptions
|
||||
): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
|
||||
const {
|
||||
filename,
|
||||
id,
|
||||
@@ -131,7 +134,7 @@ export function doCompileStyle(
|
||||
}
|
||||
|
||||
function preprocess(
|
||||
options: StyleCompileOptions,
|
||||
options: SFCStyleCompileOptions,
|
||||
preprocessor: StylePreprocessor
|
||||
): StylePreprocessorResults {
|
||||
return preprocessor.render(options.source, options.map, {
|
||||
|
||||
@@ -2,9 +2,11 @@ import {
|
||||
CompilerOptions,
|
||||
CodegenResult,
|
||||
CompilerError,
|
||||
NodeTransform
|
||||
NodeTransform,
|
||||
ParserOptions,
|
||||
RootNode
|
||||
} from '@vue/compiler-core'
|
||||
import { RawSourceMap } from 'source-map'
|
||||
import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import {
|
||||
transformAssetUrl,
|
||||
AssetURLOptions,
|
||||
@@ -14,7 +16,12 @@ import { transformSrcset } from './templateTransformSrcset'
|
||||
import { isObject } from '@vue/shared'
|
||||
import consolidate from 'consolidate'
|
||||
|
||||
export interface TemplateCompileResults {
|
||||
export interface TemplateCompiler {
|
||||
compile(template: string, options: CompilerOptions): CodegenResult
|
||||
parse(template: string, options: ParserOptions): RootNode
|
||||
}
|
||||
|
||||
export interface SFCTemplateCompileResults {
|
||||
code: string
|
||||
source: string
|
||||
tips: string[]
|
||||
@@ -22,13 +29,11 @@ export interface TemplateCompileResults {
|
||||
map?: RawSourceMap
|
||||
}
|
||||
|
||||
export interface TemplateCompiler {
|
||||
compile(template: string, options: CompilerOptions): CodegenResult
|
||||
}
|
||||
|
||||
export interface TemplateCompileOptions {
|
||||
export interface SFCTemplateCompileOptions {
|
||||
source: string
|
||||
filename: string
|
||||
ssr?: boolean
|
||||
inMap?: RawSourceMap
|
||||
compiler?: TemplateCompiler
|
||||
compilerOptions?: CompilerOptions
|
||||
preprocessLang?: string
|
||||
@@ -37,7 +42,7 @@ export interface TemplateCompileOptions {
|
||||
}
|
||||
|
||||
function preprocess(
|
||||
{ source, filename, preprocessOptions }: TemplateCompileOptions,
|
||||
{ source, filename, preprocessOptions }: SFCTemplateCompileOptions,
|
||||
preprocessor: any
|
||||
): string {
|
||||
// Consolidate exposes a callback based API, but the callback is in fact
|
||||
@@ -59,8 +64,8 @@ function preprocess(
|
||||
}
|
||||
|
||||
export function compileTemplate(
|
||||
options: TemplateCompileOptions
|
||||
): TemplateCompileResults {
|
||||
options: SFCTemplateCompileOptions
|
||||
): SFCTemplateCompileResults {
|
||||
const { preprocessLang } = options
|
||||
const preprocessor =
|
||||
preprocessLang && consolidate[preprocessLang as keyof typeof consolidate]
|
||||
@@ -100,11 +105,13 @@ export function compileTemplate(
|
||||
|
||||
function doCompileTemplate({
|
||||
filename,
|
||||
inMap,
|
||||
source,
|
||||
compiler = require('@vue/compiler-dom'),
|
||||
ssr = false,
|
||||
compiler = ssr ? require('@vue/compiler-ssr') : require('@vue/compiler-dom'),
|
||||
compilerOptions = {},
|
||||
transformAssetUrls
|
||||
}: TemplateCompileOptions): TemplateCompileResults {
|
||||
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
||||
const errors: CompilerError[] = []
|
||||
|
||||
let nodeTransforms: NodeTransform[] = []
|
||||
@@ -117,7 +124,7 @@ function doCompileTemplate({
|
||||
nodeTransforms = [transformAssetUrl, transformSrcset]
|
||||
}
|
||||
|
||||
const { code, map } = compiler.compile(source, {
|
||||
let { code, map } = compiler.compile(source, {
|
||||
mode: 'module',
|
||||
prefixIdentifiers: true,
|
||||
hoistStatic: true,
|
||||
@@ -128,5 +135,91 @@ function doCompileTemplate({
|
||||
sourceMap: true,
|
||||
onError: e => errors.push(e)
|
||||
})
|
||||
|
||||
// inMap should be the map produced by ./parse.ts which is a simple line-only
|
||||
// mapping. If it is present, we need to adjust the final map and errors to
|
||||
// reflect the original line numbers.
|
||||
if (inMap) {
|
||||
if (map) {
|
||||
map = mapLines(inMap, map)
|
||||
}
|
||||
if (errors.length) {
|
||||
patchErrors(errors, source, inMap)
|
||||
}
|
||||
}
|
||||
|
||||
return { code, source, errors, tips: [], map }
|
||||
}
|
||||
|
||||
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
||||
if (!oldMap) return newMap
|
||||
if (!newMap) return oldMap
|
||||
|
||||
const oldMapConsumer = new SourceMapConsumer(oldMap)
|
||||
const newMapConsumer = new SourceMapConsumer(newMap)
|
||||
const mergedMapGenerator = new SourceMapGenerator()
|
||||
|
||||
newMapConsumer.eachMapping(m => {
|
||||
if (m.originalLine == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const origPosInOldMap = oldMapConsumer.originalPositionFor({
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn
|
||||
})
|
||||
|
||||
if (origPosInOldMap.source == null) {
|
||||
return
|
||||
}
|
||||
|
||||
mergedMapGenerator.addMapping({
|
||||
generated: {
|
||||
line: m.generatedLine,
|
||||
column: m.generatedColumn
|
||||
},
|
||||
original: {
|
||||
line: origPosInOldMap.line, // map line
|
||||
// use current column, since the oldMap produced by @vue/compiler-sfc
|
||||
// does not
|
||||
column: m.originalColumn
|
||||
},
|
||||
source: origPosInOldMap.source,
|
||||
name: origPosInOldMap.name
|
||||
})
|
||||
})
|
||||
|
||||
// source-map's type definition is incomplete
|
||||
const generator = mergedMapGenerator as any
|
||||
;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
|
||||
generator._sources.add(sourceFile)
|
||||
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
|
||||
if (sourceContent != null) {
|
||||
mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
|
||||
}
|
||||
})
|
||||
|
||||
generator._sourceRoot = oldMap.sourceRoot
|
||||
generator._file = oldMap.file
|
||||
return generator.toJSON()
|
||||
}
|
||||
|
||||
function patchErrors(
|
||||
errors: CompilerError[],
|
||||
source: string,
|
||||
inMap: RawSourceMap
|
||||
) {
|
||||
const originalSource = inMap.sourcesContent![0]
|
||||
const offset = originalSource.indexOf(source)
|
||||
const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1
|
||||
errors.forEach(err => {
|
||||
if (err.loc) {
|
||||
err.loc.start.line += lineOffset
|
||||
err.loc.start.offset += offset
|
||||
if (err.loc.end !== err.loc.start) {
|
||||
err.loc.end.line += lineOffset
|
||||
err.loc.end.offset += offset
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,8 +14,12 @@ export {
|
||||
} from './parse'
|
||||
export {
|
||||
TemplateCompiler,
|
||||
TemplateCompileOptions,
|
||||
TemplateCompileResults
|
||||
SFCTemplateCompileOptions,
|
||||
SFCTemplateCompileResults
|
||||
} from './compileTemplate'
|
||||
export { StyleCompileOptions, StyleCompileResults } from './compileStyle'
|
||||
export { CompilerOptions, generateCodeFrame } from '@vue/compiler-core'
|
||||
export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle'
|
||||
export {
|
||||
CompilerOptions,
|
||||
CompilerError,
|
||||
generateCodeFrame
|
||||
} from '@vue/compiler-core'
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import {
|
||||
parse as baseParse,
|
||||
TextModes,
|
||||
NodeTypes,
|
||||
TextNode,
|
||||
ElementNode,
|
||||
SourceLocation
|
||||
SourceLocation,
|
||||
CompilerError,
|
||||
TextModes
|
||||
} from '@vue/compiler-core'
|
||||
import { RawSourceMap, SourceMapGenerator } from 'source-map'
|
||||
import LRUCache from 'lru-cache'
|
||||
import { generateCodeFrame } from '@vue/shared'
|
||||
import { TemplateCompiler } from './compileTemplate'
|
||||
|
||||
export interface SFCParseOptions {
|
||||
filename?: string
|
||||
sourceMap?: boolean
|
||||
sourceRoot?: string
|
||||
pad?: boolean | 'line' | 'space'
|
||||
compiler?: TemplateCompiler
|
||||
}
|
||||
|
||||
export interface SFCBlock {
|
||||
@@ -50,24 +51,32 @@ export interface SFCDescriptor {
|
||||
customBlocks: SFCBlock[]
|
||||
}
|
||||
|
||||
export interface SFCParseResult {
|
||||
descriptor: SFCDescriptor
|
||||
errors: CompilerError[]
|
||||
}
|
||||
|
||||
const SFC_CACHE_MAX_SIZE = 500
|
||||
const sourceToSFC = new LRUCache<string, SFCDescriptor>(SFC_CACHE_MAX_SIZE)
|
||||
const sourceToSFC = new LRUCache<string, SFCParseResult>(SFC_CACHE_MAX_SIZE)
|
||||
|
||||
export function parse(
|
||||
source: string,
|
||||
{
|
||||
sourceMap = true,
|
||||
filename = 'component.vue',
|
||||
sourceRoot = '',
|
||||
pad = false
|
||||
pad = false,
|
||||
compiler = require('@vue/compiler-dom')
|
||||
}: SFCParseOptions = {}
|
||||
): SFCDescriptor {
|
||||
const sourceKey = source + sourceMap + filename + sourceRoot + pad
|
||||
): SFCParseResult {
|
||||
const sourceKey =
|
||||
source + sourceMap + filename + sourceRoot + pad + compiler.parse
|
||||
const cache = sourceToSFC.get(sourceKey)
|
||||
if (cache) {
|
||||
return cache
|
||||
}
|
||||
|
||||
const sfc: SFCDescriptor = {
|
||||
const descriptor: SFCDescriptor = {
|
||||
filename,
|
||||
template: null,
|
||||
script: null,
|
||||
@@ -75,69 +84,84 @@ export function parse(
|
||||
customBlocks: []
|
||||
}
|
||||
|
||||
const ast = baseParse(source, {
|
||||
const errors: CompilerError[] = []
|
||||
const ast = compiler.parse(source, {
|
||||
// there are no components at SFC parsing level
|
||||
isNativeTag: () => true,
|
||||
getTextMode: () => TextModes.RAWTEXT
|
||||
// preserve all whitespaces
|
||||
isPreTag: () => true,
|
||||
getTextMode: (tag, _ns, parent) => {
|
||||
// all top level elements except <template> are parsed as raw text
|
||||
// containers
|
||||
if (!parent && tag !== 'template') {
|
||||
return TextModes.RAWTEXT
|
||||
} else {
|
||||
return TextModes.DATA
|
||||
}
|
||||
},
|
||||
onError: e => {
|
||||
errors.push(e)
|
||||
}
|
||||
})
|
||||
|
||||
ast.children.forEach(node => {
|
||||
if (node.type !== NodeTypes.ELEMENT) {
|
||||
return
|
||||
}
|
||||
if (!node.children.length) {
|
||||
if (!node.children.length && !hasSrc(node)) {
|
||||
return
|
||||
}
|
||||
switch (node.tag) {
|
||||
case 'template':
|
||||
if (!sfc.template) {
|
||||
sfc.template = createBlock(node, source, pad) as SFCTemplateBlock
|
||||
if (!descriptor.template) {
|
||||
descriptor.template = createBlock(
|
||||
node,
|
||||
source,
|
||||
false
|
||||
) as SFCTemplateBlock
|
||||
} else {
|
||||
warnDuplicateBlock(source, filename, node)
|
||||
}
|
||||
break
|
||||
case 'script':
|
||||
if (!sfc.script) {
|
||||
sfc.script = createBlock(node, source, pad) as SFCScriptBlock
|
||||
if (!descriptor.script) {
|
||||
descriptor.script = createBlock(node, source, pad) as SFCScriptBlock
|
||||
} else {
|
||||
warnDuplicateBlock(source, filename, node)
|
||||
}
|
||||
break
|
||||
case 'style':
|
||||
sfc.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
|
||||
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
|
||||
break
|
||||
default:
|
||||
sfc.customBlocks.push(createBlock(node, source, pad))
|
||||
descriptor.customBlocks.push(createBlock(node, source, pad))
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
if (sourceMap) {
|
||||
if (sfc.script && !sfc.script.src) {
|
||||
sfc.script.map = generateSourceMap(
|
||||
filename,
|
||||
source,
|
||||
sfc.script.content,
|
||||
sourceRoot,
|
||||
pad
|
||||
)
|
||||
}
|
||||
if (sfc.styles) {
|
||||
sfc.styles.forEach(style => {
|
||||
if (!style.src) {
|
||||
style.map = generateSourceMap(
|
||||
filename,
|
||||
source,
|
||||
style.content,
|
||||
sourceRoot,
|
||||
pad
|
||||
)
|
||||
}
|
||||
})
|
||||
const genMap = (block: SFCBlock | null) => {
|
||||
if (block && !block.src) {
|
||||
block.map = generateSourceMap(
|
||||
filename,
|
||||
source,
|
||||
block.content,
|
||||
sourceRoot,
|
||||
!pad || block.type === 'template' ? block.loc.start.line - 1 : 0
|
||||
)
|
||||
}
|
||||
}
|
||||
genMap(descriptor.template)
|
||||
genMap(descriptor.script)
|
||||
descriptor.styles.forEach(genMap)
|
||||
}
|
||||
sourceToSFC.set(sourceKey, sfc)
|
||||
|
||||
return sfc
|
||||
const result = {
|
||||
descriptor,
|
||||
errors
|
||||
}
|
||||
sourceToSFC.set(sourceKey, result)
|
||||
return result
|
||||
}
|
||||
|
||||
function warnDuplicateBlock(
|
||||
@@ -164,15 +188,26 @@ function createBlock(
|
||||
pad: SFCParseOptions['pad']
|
||||
): SFCBlock {
|
||||
const type = node.tag
|
||||
const text = node.children[0] as TextNode
|
||||
let { start, end } = node.loc
|
||||
let content = ''
|
||||
if (node.children.length) {
|
||||
start = node.children[0].loc.start
|
||||
end = node.children[node.children.length - 1].loc.end
|
||||
content = source.slice(start.offset, end.offset)
|
||||
}
|
||||
const loc = {
|
||||
source: content,
|
||||
start,
|
||||
end
|
||||
}
|
||||
const attrs: Record<string, string | true> = {}
|
||||
const block: SFCBlock = {
|
||||
type,
|
||||
content: text.content,
|
||||
loc: text.loc,
|
||||
content,
|
||||
loc,
|
||||
attrs
|
||||
}
|
||||
if (node.tag !== 'template' && pad) {
|
||||
if (pad) {
|
||||
block.content = padContent(source, block, pad) + block.content
|
||||
}
|
||||
node.props.forEach(p => {
|
||||
@@ -205,27 +240,19 @@ function generateSourceMap(
|
||||
source: string,
|
||||
generated: string,
|
||||
sourceRoot: string,
|
||||
pad?: SFCParseOptions['pad']
|
||||
lineOffset: number
|
||||
): RawSourceMap {
|
||||
const map = new SourceMapGenerator({
|
||||
file: filename.replace(/\\/g, '/'),
|
||||
sourceRoot: sourceRoot.replace(/\\/g, '/')
|
||||
})
|
||||
let offset = 0
|
||||
if (!pad) {
|
||||
offset =
|
||||
source
|
||||
.split(generated)
|
||||
.shift()!
|
||||
.split(splitRE).length - 1
|
||||
}
|
||||
map.setSourceContent(filename, source)
|
||||
generated.split(splitRE).forEach((line, index) => {
|
||||
if (!emptyRE.test(line)) {
|
||||
map.addMapping({
|
||||
source: filename,
|
||||
original: {
|
||||
line: index + 1 + offset,
|
||||
line: index + 1 + lineOffset,
|
||||
column: 0
|
||||
},
|
||||
generated: {
|
||||
@@ -252,3 +279,12 @@ function padContent(
|
||||
return Array(offset).join(padChar)
|
||||
}
|
||||
}
|
||||
|
||||
function hasSrc(node: ElementNode) {
|
||||
return node.props.some(p => {
|
||||
if (p.type !== NodeTypes.ATTRIBUTE) {
|
||||
return false
|
||||
}
|
||||
return p.name === 'src'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import postcss, { Root } from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import selectorParser, { Node, Selector } from 'postcss-selector-parser'
|
||||
|
||||
export default postcss.plugin('add-id', (options: any) => (root: Root) => {
|
||||
export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
|
||||
const id: string = options
|
||||
const keyframes = Object.create(null)
|
||||
|
||||
root.each(function rewriteSelector(node: any) {
|
||||
if (!node.selector) {
|
||||
root.each(function rewriteSelectors(node) {
|
||||
if (node.type !== 'rule') {
|
||||
// handle media queries
|
||||
if (node.type === 'atrule') {
|
||||
if (node.name === 'media' || node.name === 'supports') {
|
||||
node.each(rewriteSelector)
|
||||
node.each(rewriteSelectors)
|
||||
} else if (/-?keyframes$/.test(node.name)) {
|
||||
// register keyframes
|
||||
keyframes[node.params] = node.params = node.params + '-' + id
|
||||
@@ -18,27 +18,82 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
|
||||
}
|
||||
return
|
||||
}
|
||||
node.selector = selectorParser((selectors: any) => {
|
||||
selectors.each((selector: any) => {
|
||||
let node: any = null
|
||||
|
||||
node.selector = selectorParser(selectors => {
|
||||
function rewriteSelector(selector: Selector, slotted?: boolean) {
|
||||
let node: Node | null = null
|
||||
let shouldInject = true
|
||||
// find the last child node to insert attribute selector
|
||||
selector.each((n: any) => {
|
||||
// ">>>" combinator
|
||||
// and /deep/ alias for >>>, since >>> doesn't work in SASS
|
||||
selector.each(n => {
|
||||
// DEPRECATED ">>>" and "/deep/" combinator
|
||||
if (
|
||||
n.type === 'combinator' &&
|
||||
(n.value === '>>>' || n.value === '/deep/')
|
||||
) {
|
||||
n.value = ' '
|
||||
n.spaces.before = n.spaces.after = ''
|
||||
console.warn(
|
||||
`[@vue/compiler-sfc] the >>> and /deep/ combinators have ` +
|
||||
`been deprecated. Use ::v-deep instead.`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// in newer versions of sass, /deep/ support is also dropped, so add a ::v-deep alias
|
||||
if (n.type === 'pseudo' && n.value === '::v-deep') {
|
||||
n.value = n.spaces.before = n.spaces.after = ''
|
||||
return false
|
||||
if (n.type === 'pseudo') {
|
||||
// deep: inject [id] attribute at the node before the ::v-deep
|
||||
// combinator.
|
||||
if (n.value === '::v-deep') {
|
||||
if (n.nodes.length) {
|
||||
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
|
||||
// replace the current node with ::v-deep's inner selector
|
||||
selector.insertAfter(n, n.nodes[0])
|
||||
// insert a space combinator before if it doesn't already have one
|
||||
const prev = selector.at(selector.index(n) - 1)
|
||||
if (!prev || !isSpaceCombinator(prev)) {
|
||||
selector.insertAfter(
|
||||
n,
|
||||
selectorParser.combinator({
|
||||
value: ' '
|
||||
})
|
||||
)
|
||||
}
|
||||
selector.removeChild(n)
|
||||
} else {
|
||||
// DEPRECATED usage
|
||||
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
|
||||
console.warn(
|
||||
`[@vue/compiler-sfc] ::v-deep usage as a combinator has ` +
|
||||
`been deprecated. Use ::v-deep(<inner-selector>) instead.`
|
||||
)
|
||||
const prev = selector.at(selector.index(n) - 1)
|
||||
if (prev && isSpaceCombinator(prev)) {
|
||||
selector.removeChild(prev)
|
||||
}
|
||||
selector.removeChild(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// slot: use selector inside `::v-slotted` and inject [id + '-s']
|
||||
// instead.
|
||||
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
|
||||
if (n.value === '::v-slotted') {
|
||||
rewriteSelector(n.nodes[0] as Selector, true /* slotted */)
|
||||
selector.insertAfter(n, n.nodes[0])
|
||||
selector.removeChild(n)
|
||||
// since slotted attribute already scopes the selector there's no
|
||||
// need for the non-slot attribute.
|
||||
shouldInject = false
|
||||
return false
|
||||
}
|
||||
|
||||
// global: replace with inner selector and do not inject [id].
|
||||
// ::v-global(.foo) -> .foo
|
||||
if (n.value === '::v-global') {
|
||||
selectors.insertAfter(selector, n.nodes[0])
|
||||
selectors.removeChild(selector)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (n.type !== 'pseudo' && n.type !== 'combinator') {
|
||||
@@ -47,7 +102,7 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
|
||||
})
|
||||
|
||||
if (node) {
|
||||
node.spaces.after = ''
|
||||
;(node as Node).spaces.after = ''
|
||||
} else {
|
||||
// For deep selectors & standalone pseudo selectors,
|
||||
// the attribute selectors are prepended rather than appended.
|
||||
@@ -55,15 +110,22 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
|
||||
selector.first.spaces.before = ''
|
||||
}
|
||||
|
||||
selector.insertAfter(
|
||||
node,
|
||||
selectorParser.attribute({
|
||||
attribute: id,
|
||||
value: id,
|
||||
raws: {}
|
||||
})
|
||||
)
|
||||
})
|
||||
if (shouldInject) {
|
||||
const idToAdd = slotted ? id + '-s' : id
|
||||
selector.insertAfter(
|
||||
// If node is null it means we need to inject [id] at the start
|
||||
// insertAfter can handle `null` here
|
||||
node as any,
|
||||
selectorParser.attribute({
|
||||
attribute: idToAdd,
|
||||
value: idToAdd,
|
||||
raws: {},
|
||||
quoteMark: `"`
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
selectors.each(selector => rewriteSelector(selector as Selector))
|
||||
}).processSync(node.selector)
|
||||
})
|
||||
|
||||
@@ -99,3 +161,7 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function isSpaceCombinator(node: Node) {
|
||||
return node.type === 'combinator' && /^\s+$/.test(node.value)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
AttributeNode,
|
||||
createSimpleExpression,
|
||||
ExpressionNode,
|
||||
NodeTransform,
|
||||
@@ -7,7 +6,7 @@ import {
|
||||
SourceLocation,
|
||||
TransformContext
|
||||
} from '@vue/compiler-core'
|
||||
import { parseUrl } from './templateUtils'
|
||||
import { isRelativeUrl, parseUrl } from './templateUtils'
|
||||
|
||||
export interface AssetURLOptions {
|
||||
[name: string]: string[]
|
||||
@@ -42,10 +41,11 @@ export const transformAssetUrl: NodeTransform = (
|
||||
if ((tag === '*' || node.tag === tag) && node.props.length) {
|
||||
const attributes = options[tag]
|
||||
attributes.forEach(item => {
|
||||
node.props.forEach((attr: AttributeNode, index) => {
|
||||
node.props.forEach((attr, index) => {
|
||||
if (attr.type !== NodeTypes.ATTRIBUTE) return
|
||||
if (attr.name !== item) return
|
||||
if (!attr.value) return
|
||||
if (!isRelativeUrl(attr.value.content)) return
|
||||
const url = parseUrl(attr.value.content)
|
||||
const exp = getImportsExpressionExp(
|
||||
url.path,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
NodeTypes,
|
||||
SimpleExpressionNode
|
||||
} from '@vue/compiler-core'
|
||||
import { parseUrl } from './templateUtils'
|
||||
import { isRelativeUrl, parseUrl } from './templateUtils'
|
||||
|
||||
const srcsetTags = ['img', 'source']
|
||||
|
||||
@@ -36,31 +36,44 @@ export const transformSrcset: NodeTransform = (node, context) => {
|
||||
return { url, descriptor }
|
||||
})
|
||||
|
||||
// When srcset does not contain any relative URLs, skip transforming
|
||||
if (!imageCandidates.some(({ url }) => isRelativeUrl(url))) return
|
||||
|
||||
const compoundExpression = createCompoundExpression([], attr.loc)
|
||||
imageCandidates.forEach(({ url, descriptor }, index) => {
|
||||
const { path } = parseUrl(url)
|
||||
let exp: SimpleExpressionNode
|
||||
if (path) {
|
||||
const importsArray = Array.from(context.imports)
|
||||
const existingImportsIndex = importsArray.findIndex(
|
||||
i => i.path === path
|
||||
)
|
||||
if (existingImportsIndex > -1) {
|
||||
exp = createSimpleExpression(
|
||||
`_imports_${existingImportsIndex}`,
|
||||
false,
|
||||
attr.loc,
|
||||
true
|
||||
if (isRelativeUrl(url)) {
|
||||
const { path } = parseUrl(url)
|
||||
let exp: SimpleExpressionNode
|
||||
if (path) {
|
||||
const importsArray = Array.from(context.imports)
|
||||
const existingImportsIndex = importsArray.findIndex(
|
||||
i => i.path === path
|
||||
)
|
||||
} else {
|
||||
exp = createSimpleExpression(
|
||||
`_imports_${importsArray.length}`,
|
||||
false,
|
||||
attr.loc,
|
||||
true
|
||||
)
|
||||
context.imports.add({ exp, path })
|
||||
if (existingImportsIndex > -1) {
|
||||
exp = createSimpleExpression(
|
||||
`_imports_${existingImportsIndex}`,
|
||||
false,
|
||||
attr.loc,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
exp = createSimpleExpression(
|
||||
`_imports_${importsArray.length}`,
|
||||
false,
|
||||
attr.loc,
|
||||
true
|
||||
)
|
||||
context.imports.add({ exp, path })
|
||||
}
|
||||
compoundExpression.children.push(exp)
|
||||
}
|
||||
} else {
|
||||
const exp = createSimpleExpression(
|
||||
`"${url}"`,
|
||||
false,
|
||||
attr.loc,
|
||||
true
|
||||
)
|
||||
compoundExpression.children.push(exp)
|
||||
}
|
||||
const isNotLast = imageCandidates.length - 1 > index
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { UrlWithStringQuery, parse as uriParse } from 'url'
|
||||
import { isString } from '@vue/shared'
|
||||
|
||||
export function isRelativeUrl(url: string): boolean {
|
||||
const firstChar = url.charAt(0)
|
||||
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
||||
}
|
||||
|
||||
// We need an extra transform context API for injecting arbitrary import
|
||||
// statements.
|
||||
export function parseUrl(url: string): UrlWithStringQuery {
|
||||
const firstChar = url.charAt(0)
|
||||
if (firstChar === '.' || firstChar === '~' || firstChar === '@') {
|
||||
if (firstChar === '~') {
|
||||
const secondChar = url.charAt(1)
|
||||
url = url.slice(secondChar === '/' ? 2 : 1)
|
||||
}
|
||||
if (firstChar === '~') {
|
||||
const secondChar = url.charAt(1)
|
||||
url = url.slice(secondChar === '/' ? 2 : 1)
|
||||
}
|
||||
return parseUriParts(url)
|
||||
}
|
||||
|
||||
1
packages/compiler-ssr/README.md
Normal file
1
packages/compiler-ssr/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# @vue/compiler-ssr
|
||||
299
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Normal file
299
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { compile } from '../src'
|
||||
|
||||
describe('ssr: components', () => {
|
||||
test('basic', () => {
|
||||
expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, {
|
||||
id: \\"a\\",
|
||||
prop: _ctx.b
|
||||
}, null, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('dynamic component', () => {
|
||||
expect(compile(`<component is="foo" prop="b" />`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveDynamicComponent: _resolveDynamicComponent, withCtx: _withCtx } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), { prop: \\"b\\" }, null, _parent))
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(compile(`<component :is="foo" prop="b" />`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveDynamicComponent: _resolveDynamicComponent, withCtx: _withCtx } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), { prop: \\"b\\" }, null, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('slots', () => {
|
||||
test('implicit default slot', () => {
|
||||
expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`hello<div\${_scopeId}></div>\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(\\"hello\\"),
|
||||
_createVNode(\\"div\\")
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('explicit default slot', () => {
|
||||
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: _withCtx(({ msg }, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(_toDisplayString(msg + _ctx.outer), 1 /* TEXT */)
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('named slots', () => {
|
||||
expect(
|
||||
compile(`<foo>
|
||||
<template v-slot>foo</template>
|
||||
<template v-slot:named>bar</template>
|
||||
</foo>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`foo\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(\\"foo\\")
|
||||
]
|
||||
}
|
||||
}),
|
||||
named: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`bar\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(\\"bar\\")
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('v-if slot', () => {
|
||||
expect(
|
||||
compile(`<foo>
|
||||
<template v-slot:named v-if="ok">foo</template>
|
||||
</foo>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [
|
||||
(_ctx.ok)
|
||||
? {
|
||||
name: \\"named\\",
|
||||
fn: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`foo\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(\\"foo\\")
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
: undefined
|
||||
]), _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('v-for slot', () => {
|
||||
expect(
|
||||
compile(`<foo>
|
||||
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
|
||||
</foo>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [
|
||||
_renderList(_ctx.names, (key) => {
|
||||
return {
|
||||
name: key,
|
||||
fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
|
||||
} else {
|
||||
return [
|
||||
_createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */)
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]), _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('nested transform scoping in vnode branch', () => {
|
||||
expect(
|
||||
compile(`<foo>
|
||||
<template v-slot:foo="{ list }">
|
||||
<div v-if="ok">
|
||||
<span v-for="i in list"></span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:bar="{ ok }">
|
||||
<div v-if="ok">
|
||||
<span v-for="i in list"></span>
|
||||
</div>
|
||||
</template>
|
||||
</foo>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
foo: _withCtx(({ list }, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
if (_ctx.ok) {
|
||||
_push(\`<div\${_scopeId}><!--[-->\`)
|
||||
_ssrRenderList(list, (i) => {
|
||||
_push(\`<span\${_scopeId}></span>\`)
|
||||
})
|
||||
_push(\`<!--]--></div>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
(_ctx.ok)
|
||||
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, [
|
||||
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createBlock(\\"span\\"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
: _createCommentVNode(\\"v-if\\", true)
|
||||
]
|
||||
}
|
||||
}),
|
||||
bar: _withCtx(({ ok }, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
if (ok) {
|
||||
_push(\`<div\${_scopeId}><!--[-->\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<span\${_scopeId}></span>\`)
|
||||
})
|
||||
_push(\`<!--]--></div>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
ok
|
||||
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, [
|
||||
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (i) => {
|
||||
return (_openBlock(), _createBlock(\\"span\\"))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
: _createCommentVNode(\\"v-if\\", true)
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('built-in fallthroughs', () => {
|
||||
expect(compile(`<transition><div/></transition>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_push(\`<div></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(compile(`<transition-group><div/></transition-group>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_push(\`<!--[--><div></div><!--]-->\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
const _component_foo = _resolveComponent(\\"foo\\")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, null, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
218
packages/compiler-ssr/__tests__/ssrElement.spec.ts
Normal file
218
packages/compiler-ssr/__tests__/ssrElement.spec.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { getCompiledString } from './utils'
|
||||
import { compile } from '../src'
|
||||
|
||||
describe('ssr: element', () => {
|
||||
test('basic elements', () => {
|
||||
expect(getCompiledString(`<div></div>`)).toMatchInlineSnapshot(
|
||||
`"\`<div></div>\`"`
|
||||
)
|
||||
expect(getCompiledString(`<div/>`)).toMatchInlineSnapshot(
|
||||
`"\`<div></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('nested elements', () => {
|
||||
expect(
|
||||
getCompiledString(`<div><span></span><span></span></div>`)
|
||||
).toMatchInlineSnapshot(`"\`<div><span></span><span></span></div>\`"`)
|
||||
})
|
||||
|
||||
test('void element', () => {
|
||||
expect(getCompiledString(`<input>`)).toMatchInlineSnapshot(`"\`<input>\`"`)
|
||||
})
|
||||
|
||||
describe('children override', () => {
|
||||
test('v-html', () => {
|
||||
expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(
|
||||
`"\`<div>\${_ctx.foo}</div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('v-text', () => {
|
||||
expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(
|
||||
`"\`<div>\${_ssrInterpolate(_ctx.foo)}</div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('<textarea> with dynamic value', () => {
|
||||
expect(
|
||||
getCompiledString(`<textarea :value="foo"/>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<textarea>\${_ssrInterpolate(_ctx.foo)}</textarea>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('<textarea> with static value', () => {
|
||||
expect(
|
||||
getCompiledString(`<textarea value="fo>o"/>`)
|
||||
).toMatchInlineSnapshot(`"\`<textarea>fo>o</textarea>\`"`)
|
||||
})
|
||||
|
||||
test('<textarea> with dynamic v-bind', () => {
|
||||
expect(compile(`<textarea v-bind="obj">fallback</textarea>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
let _temp0
|
||||
|
||||
_push(\`<textarea\${
|
||||
_ssrRenderAttrs(_temp0 = _ctx.obj, \\"textarea\\")
|
||||
}>\${
|
||||
_ssrInterpolate((\\"value\\" in _temp0) ? _temp0.value : \\"fallback\\")
|
||||
}</textarea>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('should pass tag to custom elements w/ dynamic v-bind', () => {
|
||||
expect(
|
||||
compile(`<my-foo v-bind="obj"></my-foo>`, {
|
||||
isCustomElement: () => true
|
||||
}).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_push(\`<my-foo\${_ssrRenderAttrs(_ctx.obj, \\"my-foo\\")}></my-foo>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('attrs', () => {
|
||||
test('static attrs', () => {
|
||||
expect(
|
||||
getCompiledString(`<div id="foo" class="bar"></div>`)
|
||||
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`)
|
||||
})
|
||||
|
||||
test('v-bind:class', () => {
|
||||
expect(
|
||||
getCompiledString(`<div id="foo" :class="bar"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div id=\\"foo\\" class=\\"\${_ssrRenderClass(_ctx.bar)}\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('static class + v-bind:class', () => {
|
||||
expect(
|
||||
getCompiledString(`<div class="foo" :class="bar"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div class=\\"\${_ssrRenderClass([_ctx.bar, \\"foo\\"])}\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('v-bind:style', () => {
|
||||
expect(
|
||||
getCompiledString(`<div id="foo" :style="bar"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div id=\\"foo\\" style=\\"\${_ssrRenderStyle(_ctx.bar)}\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('static style + v-bind:style', () => {
|
||||
expect(
|
||||
getCompiledString(`<div style="color:red;" :style="bar"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div style=\\"\${_ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])}\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('v-bind:key (boolean)', () => {
|
||||
expect(
|
||||
getCompiledString(`<input type="checkbox" :checked="checked">`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<input type=\\"checkbox\\"\${(_ctx.checked) ? \\" checked\\" : \\"\\"}>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('v-bind:key (non-boolean)', () => {
|
||||
expect(
|
||||
getCompiledString(`<div :id="id" class="bar"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div\${_ssrRenderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"`
|
||||
)
|
||||
})
|
||||
|
||||
test('v-bind:[key]', () => {
|
||||
expect(
|
||||
getCompiledString(`<div v-bind:[key]="value"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div\${_ssrRenderAttrs({ [_ctx.key]: _ctx.value })}></div>\`"`
|
||||
)
|
||||
|
||||
expect(getCompiledString(`<div class="foo" v-bind:[key]="value"></div>`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${_ssrRenderAttrs({
|
||||
class: \\"foo\\",
|
||||
[_ctx.key]: _ctx.value
|
||||
})}></div>\`"
|
||||
`)
|
||||
|
||||
expect(getCompiledString(`<div :id="id" v-bind:[key]="value"></div>`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${_ssrRenderAttrs({
|
||||
id: _ctx.id,
|
||||
[_ctx.key]: _ctx.value
|
||||
})}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('v-bind="obj"', () => {
|
||||
expect(
|
||||
getCompiledString(`<div v-bind="obj"></div>`)
|
||||
).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.obj)}></div>\`"`)
|
||||
|
||||
expect(
|
||||
getCompiledString(`<div class="foo" v-bind="obj"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div\${_ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ctx.obj))}></div>\`"`
|
||||
)
|
||||
|
||||
expect(
|
||||
getCompiledString(`<div :id="id" v-bind="obj"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div\${_ssrRenderAttrs(_mergeProps({ id: _ctx.id }, _ctx.obj))}></div>\`"`
|
||||
)
|
||||
|
||||
// dynamic key + v-bind="object"
|
||||
expect(
|
||||
getCompiledString(`<div :[key]="id" v-bind="obj"></div>`)
|
||||
).toMatchInlineSnapshot(
|
||||
`"\`<div\${_ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))}></div>\`"`
|
||||
)
|
||||
|
||||
// should merge class and :class
|
||||
expect(getCompiledString(`<div class="a" :class="b" v-bind="obj"></div>`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"\`<div\${_ssrRenderAttrs(_mergeProps({
|
||||
class: [\\"a\\", _ctx.b]
|
||||
}, _ctx.obj))}></div>\`"
|
||||
`)
|
||||
|
||||
// should merge style and :style
|
||||
expect(
|
||||
getCompiledString(
|
||||
`<div style="color:red;" :style="b" v-bind="obj"></div>`
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
"\`<div\${_ssrRenderAttrs(_mergeProps({
|
||||
style: [{\\"color\\":\\"red\\"}, _ctx.b]
|
||||
}, _ctx.obj))}></div>\`"
|
||||
`)
|
||||
})
|
||||
|
||||
test('should ignore v-on', () => {
|
||||
expect(
|
||||
getCompiledString(`<div id="foo" @click="bar"/>`)
|
||||
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
|
||||
expect(
|
||||
getCompiledString(`<div id="foo" v-on="bar"/>`)
|
||||
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
|
||||
expect(
|
||||
getCompiledString(`<div v-bind="foo" v-on="bar"/>`)
|
||||
).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.foo)}></div>\`"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
43
packages/compiler-ssr/__tests__/ssrPortal.spec.ts
Normal file
43
packages/compiler-ssr/__tests__/ssrPortal.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { compile } from '../src'
|
||||
|
||||
describe('ssr compile: teleport', () => {
|
||||
test('should work', () => {
|
||||
expect(compile(`<teleport :target="target"><div/></teleport>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_ssrRenderTeleport(_push, (_push) => {
|
||||
_push(\`<div></div>\`)
|
||||
}, _ctx.target, false, _parent)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('disabled prop handling', () => {
|
||||
expect(
|
||||
compile(`<teleport :target="target" disabled><div/></teleport>`).code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_ssrRenderTeleport(_push, (_push) => {
|
||||
_push(\`<div></div>\`)
|
||||
}, _ctx.target, true, _parent)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compile(`<teleport :target="target" :disabled="foo"><div/></teleport>`)
|
||||
.code
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent) {
|
||||
_ssrRenderTeleport(_push, (_push) => {
|
||||
_push(\`<div></div>\`)
|
||||
}, _ctx.target, _ctx.foo, _parent)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user