Skip to content

Commit f632033

Browse files
committed
add support for functional templates
1 parent d57f942 commit f632033

File tree

5 files changed

+102
-6
lines changed

5 files changed

+102
-6
lines changed

flow/compiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,6 @@ declare type SFCBlock = {
173173
lang?: string;
174174
src?: string;
175175
scoped?: boolean;
176+
functional?: boolean;
176177
module?: string | boolean;
177178
}

src/core/vdom/create-component.js

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
/* @flow */
22

3-
import VNode from './vnode'
3+
import {
4+
warn,
5+
isObject,
6+
hasOwn,
7+
hyphenate,
8+
validateProp,
9+
toNumber,
10+
_toString,
11+
looseEqual,
12+
emptyObject,
13+
looseIndexOf
14+
} from '../util/index'
15+
16+
import VNode, {
17+
createTextVNode,
18+
createEmptyVNode
19+
} from '../vdom/vnode'
20+
421
import { resolveConstructorOptions } from '../instance/init'
522
import { activeInstance, callHook } from '../instance/lifecycle'
6-
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
723
import { createElement } from './create-element'
8-
import { warn, isObject, hasOwn, hyphenate, validateProp } from '../util/index'
24+
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
25+
import { renderList } from '../instance/render-helpers/render-list'
26+
import { renderSlot } from '../instance/render-helpers/render-slot'
27+
import { resolveFilter } from '../instance/render-helpers/resolve-filter'
28+
import { checkKeyCodes } from '../instance/render-helpers/check-keycodes'
29+
import { bindObjectProps } from '../instance/render-helpers/bind-object-props'
30+
import { renderStatic, markOnce } from '../instance/render-helpers/render-static'
31+
import { resolveScopedSlots } from '../instance/render-helpers/resolve-slots'
932

1033
const hooks = { init, prepatch, insert, destroy }
1134
const hooksToMerge = Object.keys(hooks)
@@ -102,8 +125,9 @@ function createFunctionalComponent (
102125
context: Component,
103126
children: ?Array<VNode>
104127
): VNode | void {
128+
const options = Ctor.options
105129
const props = {}
106-
const propOptions = Ctor.options.props
130+
const propOptions = options.props
107131
if (propOptions) {
108132
for (const key in propOptions) {
109133
props[key] = validateProp(key, propOptions, propsData)
@@ -113,13 +137,52 @@ function createFunctionalComponent (
113137
// gets a unique context - this is necessary for correct named slot check
114138
const _context = Object.create(context)
115139
const h = (a, b, c, d) => createElement(_context, a, b, c, d, true)
116-
const vnode = Ctor.options.render.call(null, h, {
140+
141+
const renderContext: Object = {
117142
props,
118143
data,
119144
parent: context,
120145
children,
121146
slots: () => resolveSlots(children, context)
122-
})
147+
}
148+
149+
// functional template runtime check
150+
if (process.env.NODE_ENV !== 'production' && options.template) {
151+
return warn(
152+
'Vue templates with functional components are not supported with runtime build.'
153+
)
154+
}
155+
156+
// functional compiled template
157+
if (options.compiled) {
158+
renderContext.$slots = renderContext.slots()
159+
renderContext.$scopedSlots = data.scopedSlots || emptyObject
160+
renderContext._o = markOnce
161+
renderContext._n = toNumber
162+
renderContext._s = _toString
163+
renderContext._l = renderList
164+
renderContext._q = looseEqual
165+
renderContext._i = looseIndexOf
166+
renderContext._f = resolveFilter
167+
renderContext._k = checkKeyCodes
168+
renderContext._v = createTextVNode
169+
renderContext._e = createEmptyVNode
170+
renderContext._u = resolveScopedSlots
171+
// Apply context to render helpers
172+
renderContext._c = (a, b, c, d) => createElement(renderContext, a, b, c, d, false)
173+
renderContext._t = (a, b, c, d) => renderSlot.call(renderContext, a, b, c, d)
174+
renderContext._m = (a, b) => renderStatic.call(renderContext, a, b)
175+
renderContext._b = (a, b, c, d) => bindObjectProps.call(renderContext, a, b, c, d)
176+
177+
// Add static nodes
178+
renderContext._staticTrees = []
179+
const staticRenderFns = options.staticRenderFns || []
180+
for (let i = 0; i < staticRenderFns.length; i++) {
181+
renderContext._staticTrees.push(staticRenderFns[i](null, renderContext))
182+
}
183+
}
184+
185+
const vnode = options.render.call(null, h, renderContext)
123186
if (vnode instanceof VNode) {
124187
vnode.functionalContext = context
125188
if (data.slot) {

src/sfc/parser.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ export function parseComponent (
7575
if (attr.name === 'scoped') {
7676
block.scoped = true
7777
}
78+
if (attr.name === 'functional') {
79+
block.functional = true
80+
}
7881
if (attr.name === 'module') {
7982
block.module = attr.value || true
8083
}

test/unit/features/options/functional.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,19 @@ describe('Options functional', () => {
113113
vm.$destroy()
114114
}).then(done)
115115
})
116+
117+
it('should warn when using runtime compiled templates', () => {
118+
new Vue({
119+
template: `<div><test></test></div>`,
120+
components: {
121+
test: {
122+
functional: true,
123+
template: `<div></div>`
124+
}
125+
}
126+
}).$mount()
127+
128+
expect('Vue templates with functional components are not supported with runtime build.')
129+
.toHaveBeenWarned()
130+
})
116131
})

test/unit/modules/sfc/sfc-parser.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,18 @@ describe('Single File Component parser', () => {
150150
const res = parseComponent(`<template>${raw}</template>`)
151151
expect(res.template.content.trim()).toBe(raw)
152152
})
153+
154+
it('should parse functional template', () => {
155+
const res = parseComponent(`
156+
<template functional>
157+
<div>hi</div>
158+
</template>
159+
<script>
160+
export default {}
161+
</script>
162+
`)
163+
expect(res.template.content.trim()).toBe('<div>hi</div>')
164+
expect(res.template.functional).toBe(true)
165+
expect(res.script.content.trim()).toBe('export default {}')
166+
})
153167
})

0 commit comments

Comments
 (0)