export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION
- params: ExpressionNode | ExpressionNode[] | undefined
+ params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
body?: BlockStatement
newline: boolean
transformExpression,
processExpression
} from './transforms/transformExpression'
-export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
+export {
+ buildSlots,
+ SlotFnBuilder,
+ trackVForSlotScopes,
+ trackSlotScopes
+} from './transforms/vSlot'
export { resolveComponentType, buildProps } from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
}
}
+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
hasDynamicSlots: boolean
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
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
)
}
- slotsProperties.push(buildDefaultSlot(exp, children, loc))
+ slotsProperties.push(buildDefaultSlotProperty(exp, children))
}
// 2. Iterate through children and check for template slots
hasDynamicSlots = true
}
- const slotFunction = createFunctionExpression(
- slotProps,
- slotChildren,
- false /* newline */,
- true /* isSlot */,
- 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
if (!onComponentDefaultSlot) {
if (!hasTemplateSlots) {
// implicit default slot (on component)
- slotsProperties.push(buildDefaultSlot(undefined, children, loc))
+ slotsProperties.push(buildDefaultSlotProperty(undefined, children))
} else if (implicitDefaultChildren.length) {
// implicit default slot (mixed with named slots)
if (hasNamedDefaultSlot) {
)
} else {
slotsProperties.push(
- buildDefaultSlot(undefined, implicitDefaultChildren, loc)
+ buildDefaultSlotProperty(undefined, implicitDefaultChildren)
)
}
}
}
}
-function buildDefaultSlot(
- slotProps: ExpressionNode | undefined,
- children: TemplateChildNode[],
- loc: SourceLocation
-): Property {
- return createObjectProperty(
- `default`,
- createFunctionExpression(
- slotProps,
- children,
- false /* newline */,
- true /* isSlot */,
- children.length ? children[0].loc : loc
- )
- )
-}
-
function buildDynamicSlot(
name: ExpressionNode,
fn: FunctionExpression
import { registerRuntimeHelpers } from '@vue/compiler-dom'
-export const SSR_INTERPOLATE = Symbol(`interpolate`)
-export const SSR_RENDER_COMPONENT = Symbol(`renderComponent`)
-export const SSR_RENDER_SLOT = Symbol(`renderSlot`)
-export const SSR_RENDER_CLASS = Symbol(`renderClass`)
-export const SSR_RENDER_STYLE = Symbol(`renderStyle`)
-export const SSR_RENDER_ATTRS = Symbol(`renderAttrs`)
-export const SSR_RENDER_ATTR = Symbol(`renderAttr`)
-export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`renderDynamicAttr`)
-export const SSR_RENDER_LIST = Symbol(`renderList`)
-export const SSR_LOOSE_EQUAL = Symbol(`looseEqual`)
-export const SSR_LOOSE_CONTAIN = Symbol(`looseContain`)
-export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`renderDynamicModel`)
-export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`getDynamicModelProps`)
+export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
+export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
+export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
+export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
+export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`)
+export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`)
+export const SSR_RENDER_ATTR = Symbol(`ssrRenderAttr`)
+export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`ssrRenderDynamicAttr`)
+export const SSR_RENDER_LIST = Symbol(`ssrRenderList`)
+export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`)
+export const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`)
+export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
+export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
export const ssrHelpers = {
- [SSR_INTERPOLATE]: `_interpolate`,
- [SSR_RENDER_COMPONENT]: `_renderComponent`,
- [SSR_RENDER_SLOT]: `_renderSlot`,
- [SSR_RENDER_CLASS]: `_renderClass`,
- [SSR_RENDER_STYLE]: `_renderStyle`,
- [SSR_RENDER_ATTRS]: `_renderAttrs`,
- [SSR_RENDER_ATTR]: `_renderAttr`,
- [SSR_RENDER_DYNAMIC_ATTR]: `_renderDynamicAttr`,
- [SSR_RENDER_LIST]: `_renderList`,
- [SSR_LOOSE_EQUAL]: `_looseEqual`,
- [SSR_LOOSE_CONTAIN]: `_looseContain`,
- [SSR_RENDER_DYNAMIC_MODEL]: `_renderDynamicModel`,
- [SSR_GET_DYNAMIC_MODEL_PROPS]: `_getDynamicModelProps`
+ [SSR_INTERPOLATE]: `_ssrInterpolate`,
+ [SSR_RENDER_COMPONENT]: `_ssrRenderComponent`,
+ [SSR_RENDER_SLOT]: `_ssrRenderSlot`,
+ [SSR_RENDER_CLASS]: `_ssrRenderClass`,
+ [SSR_RENDER_STYLE]: `_ssrRenderStyle`,
+ [SSR_RENDER_ATTRS]: `_ssrRenderAttrs`,
+ [SSR_RENDER_ATTR]: `_ssrRenderAttr`,
+ [SSR_RENDER_DYNAMIC_ATTR]: `_ssrRenderDynamicAttr`,
+ [SSR_RENDER_LIST]: `_ssrRenderList`,
+ [SSR_LOOSE_EQUAL]: `_ssrLooseEqual`,
+ [SSR_LOOSE_CONTAIN]: `_ssrLooseContain`,
+ [SSR_RENDER_DYNAMIC_MODEL]: `_ssrRenderDynamicModel`,
+ [SSR_GET_DYNAMIC_MODEL_PROPS]: `_ssrGetDynamicModelProps`
}
// Note: these are helpers imported from @vue/server-renderer
-import { interpolate } from '../src/helpers/interpolate'
+import { ssrInterpolate } from '../src/helpers/ssrInterpolate'
import { escapeHtml } from '@vue/shared'
test('ssr: interpolate', () => {
- expect(interpolate(0)).toBe(`0`)
- expect(interpolate(`foo`)).toBe(`foo`)
- expect(interpolate(`<div>`)).toBe(`<div>`)
+ expect(ssrInterpolate(0)).toBe(`0`)
+ expect(ssrInterpolate(`foo`)).toBe(`foo`)
+ expect(ssrInterpolate(`<div>`)).toBe(`<div>`)
// should escape interpolated values
- expect(interpolate([1, 2, 3])).toBe(
+ expect(ssrInterpolate([1, 2, 3])).toBe(
escapeHtml(JSON.stringify([1, 2, 3], null, 2))
)
expect(
- interpolate({
+ ssrInterpolate({
foo: 1,
bar: `<div>`
})
import {
- renderAttrs,
- renderClass,
- renderStyle,
- renderAttr
-} from '../src/helpers/renderAttrs'
+ ssrRenderAttrs,
+ ssrRenderClass,
+ ssrRenderStyle,
+ ssrRenderAttr
+} from '../src/helpers/ssrRenderAttrs'
import { escapeHtml } from '@vue/shared'
describe('ssr: renderAttrs', () => {
test('ignore reserved props', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
key: 1,
ref: () => {},
onClick: () => {}
test('normal attrs', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
id: 'foo',
title: 'bar'
})
test('escape attrs', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
id: '"><script'
})
).toBe(` id=""><script"`)
test('boolean attrs', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
checked: true,
multiple: false
})
test('ignore falsy values', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
foo: false,
title: null,
baz: undefined
test('ingore non-renderable values', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
foo: {},
bar: [],
baz: () => {}
test('props to attrs', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
readOnly: true, // simple lower case conversion
htmlFor: 'foobar' // special cases
})
test('preserve name on custom element', () => {
expect(
- renderAttrs(
+ ssrRenderAttrs(
{
fooBar: 'ok'
},
describe('ssr: renderAttr', () => {
test('basic', () => {
- expect(renderAttr('foo', 'bar')).toBe(` foo="bar"`)
+ expect(ssrRenderAttr('foo', 'bar')).toBe(` foo="bar"`)
})
test('null and undefined', () => {
- expect(renderAttr('foo', null)).toBe(``)
- expect(renderAttr('foo', undefined)).toBe(``)
+ expect(ssrRenderAttr('foo', null)).toBe(``)
+ expect(ssrRenderAttr('foo', undefined)).toBe(``)
})
test('escape', () => {
- expect(renderAttr('foo', '<script>')).toBe(
+ expect(ssrRenderAttr('foo', '<script>')).toBe(
` foo="${escapeHtml(`<script>`)}"`
)
})
describe('ssr: renderClass', () => {
test('via renderProps', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
class: ['foo', 'bar']
})
).toBe(` class="foo bar"`)
})
test('standalone', () => {
- expect(renderClass(`foo`)).toBe(`foo`)
- expect(renderClass([`foo`, `bar`])).toBe(`foo bar`)
- expect(renderClass({ foo: true, bar: false })).toBe(`foo`)
- expect(renderClass([{ foo: true, bar: false }, `baz`])).toBe(`foo baz`)
+ expect(ssrRenderClass(`foo`)).toBe(`foo`)
+ expect(ssrRenderClass([`foo`, `bar`])).toBe(`foo bar`)
+ expect(ssrRenderClass({ foo: true, bar: false })).toBe(`foo`)
+ expect(ssrRenderClass([{ foo: true, bar: false }, `baz`])).toBe(`foo baz`)
})
test('escape class values', () => {
- expect(renderClass(`"><script`)).toBe(`"><script`)
+ expect(ssrRenderClass(`"><script`)).toBe(`"><script`)
})
})
describe('ssr: renderStyle', () => {
test('via renderProps', () => {
expect(
- renderAttrs({
+ ssrRenderAttrs({
style: {
color: 'red'
}
})
test('standalone', () => {
- expect(renderStyle(`color:red`)).toBe(`color:red`)
+ expect(ssrRenderStyle(`color:red`)).toBe(`color:red`)
expect(
- renderStyle({
+ ssrRenderStyle({
color: `red`
})
).toBe(`color:red;`)
expect(
- renderStyle([
+ ssrRenderStyle([
{ color: `red` },
{ fontSize: `15px` } // case conversion
])
test('number handling', () => {
expect(
- renderStyle({
+ ssrRenderStyle({
fontSize: 15, // should be ignored since font-size requires unit
opacity: 0.5
})
})
test('escape inline CSS', () => {
- expect(renderStyle(`"><script`)).toBe(`"><script`)
+ expect(ssrRenderStyle(`"><script`)).toBe(`"><script`)
expect(
- renderStyle({
+ ssrRenderStyle({
color: `"><script`
})
).toBe(`color:"><script;`)
} from 'vue'
import { escapeHtml } from '@vue/shared'
import { renderToString, renderComponent } from '../src/renderToString'
-import { renderSlot } from '../src/helpers/renderSlot'
+import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
describe('ssr: renderToString', () => {
test('should apply app context', async () => {
props: ['msg'],
ssrRender(ctx: any, push: any, parent: any) {
push(`<div class="child">`)
- renderSlot(
+ ssrRenderSlot(
ctx.$slots,
'default',
{ msg: 'from slot' },
props: ['msg'],
ssrRender(ctx: any, push: any, parent: any) {
push(`<div class="child">`)
- renderSlot(
+ ssrRenderSlot(
ctx.$slots,
'default',
{ msg: 'from slot' },
--- /dev/null
+import {
+ ssrRenderDynamicModel,
+ ssrGetDynamicModelProps
+ // ssrGetDynamicModelProps
+} from '../src/helpers/ssrVModelHelpers'
+
+describe('ssr: v-model helpers', () => {
+ test('ssrRenderDynamicModel', () => {
+ expect(ssrRenderDynamicModel(null, 'foo', null)).toBe(` value="foo"`)
+ expect(ssrRenderDynamicModel('text', 'foo', null)).toBe(` value="foo"`)
+ expect(ssrRenderDynamicModel('email', 'foo', null)).toBe(` value="foo"`)
+
+ expect(ssrRenderDynamicModel('checkbox', true, null)).toBe(` checked`)
+ expect(ssrRenderDynamicModel('checkbox', false, null)).toBe(``)
+ expect(ssrRenderDynamicModel('checkbox', [1], '1')).toBe(` checked`)
+ expect(ssrRenderDynamicModel('checkbox', [1], 1)).toBe(` checked`)
+ expect(ssrRenderDynamicModel('checkbox', [1], 0)).toBe(``)
+
+ expect(ssrRenderDynamicModel('radio', 'foo', 'foo')).toBe(` checked`)
+ expect(ssrRenderDynamicModel('radio', 1, '1')).toBe(` checked`)
+ expect(ssrRenderDynamicModel('radio', 1, 0)).toBe(``)
+ })
+
+ test('ssrGetDynamicModelProps', () => {
+ expect(ssrGetDynamicModelProps({}, 'foo')).toMatchObject({ value: 'foo' })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'text'
+ },
+ 'foo'
+ )
+ ).toMatchObject({ value: 'foo' })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'email'
+ },
+ 'foo'
+ )
+ ).toMatchObject({ value: 'foo' })
+
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'checkbox'
+ },
+ true
+ )
+ ).toMatchObject({ checked: true })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'checkbox'
+ },
+ false
+ )
+ ).toBe(null)
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'checkbox',
+ value: '1'
+ },
+ [1]
+ )
+ ).toMatchObject({ checked: true })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'checkbox',
+ value: 1
+ },
+ [1]
+ )
+ ).toMatchObject({ checked: true })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'checkbox',
+ value: 0
+ },
+ [1]
+ )
+ ).toBe(null)
+
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'radio',
+ value: 'foo'
+ },
+ 'foo'
+ )
+ ).toMatchObject({ checked: true })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'radio',
+ value: '1'
+ },
+ 1
+ )
+ ).toMatchObject({ checked: true })
+ expect(
+ ssrGetDynamicModelProps(
+ {
+ type: 'radio',
+ value: 0
+ },
+ 1
+ )
+ ).toBe(null)
+ })
+})
import { escapeHtml, toDisplayString } from '@vue/shared'
-export function interpolate(value: unknown): string {
+export function ssrInterpolate(value: unknown): string {
return escapeHtml(toDisplayString(value))
}
const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`)
-export function renderAttrs(
+export function ssrRenderAttrs(
props: Record<string, unknown>,
tag?: string
): string {
}
const value = props[key]
if (key === 'class') {
- ret += ` class="${renderClass(value)}"`
+ ret += ` class="${ssrRenderClass(value)}"`
} else if (key === 'style') {
- ret += ` style="${renderStyle(value)}"`
+ ret += ` style="${ssrRenderStyle(value)}"`
} else {
- ret += renderDynamicAttr(key, value, tag)
+ ret += ssrRenderDynamicAttr(key, value, tag)
}
}
return ret
}
// render an attr with dynamic (unknown) key.
-export function renderDynamicAttr(
+export function ssrRenderDynamicAttr(
key: string,
value: unknown,
tag?: string
// Render a v-bind attr with static key. The key is pre-processed at compile
// time and we only need to check and escape value.
-export function renderAttr(key: string, value: unknown): string {
+export function ssrRenderAttr(key: string, value: unknown): string {
if (!isRenderableValue(value)) {
return ``
}
return type === 'string' || type === 'number' || type === 'boolean'
}
-export function renderClass(raw: unknown): string {
+export function ssrRenderClass(raw: unknown): string {
return escapeHtml(normalizeClass(raw))
}
-export function renderStyle(raw: unknown): string {
+export function ssrRenderStyle(raw: unknown): string {
if (!raw) {
return ''
}
import { isArray, isString, isObject } from '@vue/shared'
-export function renderList(
+export function ssrRenderList(
source: unknown,
renderItem: (value: unknown, key: string | number, index?: number) => void
) {
parentComponent: ComponentInternalInstance | null
) => void
-export function renderSlot(
+export function ssrRenderSlot(
slots: Slots | SSRSlots,
slotName: string,
slotProps: Props,
-import { looseEqual as _looseEqual, looseIndexOf } from '@vue/shared'
-import { renderAttr } from './renderAttrs'
+import { looseEqual, looseIndexOf } from '@vue/shared'
+import { ssrRenderAttr } from './ssrRenderAttrs'
-export const looseEqual = _looseEqual as (a: unknown, b: unknown) => boolean
+export const ssrLooseEqual = looseEqual as (a: unknown, b: unknown) => boolean
-export function looseContain(arr: unknown[], value: unknown): boolean {
+export function ssrLooseContain(arr: unknown[], value: unknown): boolean {
return looseIndexOf(arr, value) > -1
}
// for <input :type="type" v-model="model" value="value">
-export function renderDynamicModel(
+export function ssrRenderDynamicModel(
type: unknown,
model: unknown,
value: unknown
) {
switch (type) {
case 'radio':
- return _looseEqual(model, value) ? ' checked' : ''
+ return looseEqual(model, value) ? ' checked' : ''
case 'checkbox':
return (Array.isArray(model)
- ? looseContain(model, value)
+ ? ssrLooseContain(model, value)
: model)
? ' checked'
: ''
default:
// text types
- return renderAttr('value', model)
+ return ssrRenderAttr('value', model)
}
}
// for <input v-bind="obj" v-model="model">
-export function getDynamicModelProps(existingProps: any = {}, model: unknown) {
+export function ssrGetDynamicModelProps(
+ existingProps: any = {},
+ model: unknown
+) {
const { type, value } = existingProps
switch (type) {
case 'radio':
- return _looseEqual(model, value) ? { checked: true } : null
+ return looseEqual(model, value) ? { checked: true } : null
case 'checkbox':
return (Array.isArray(model)
- ? looseContain(model, value)
+ ? ssrLooseContain(model, value)
: model)
? { checked: true }
: null
export { renderToString } from './renderToString'
// internal runtime helpers
-export { renderComponent as _renderComponent } from './renderToString'
-export { renderSlot as _renderSlot } from './helpers/renderSlot'
+export { renderComponent as _ssrRenderComponent } from './renderToString'
+export { ssrRenderSlot as _ssrRenderSlot } from './helpers/ssrRenderSlot'
export {
- renderClass as _renderClass,
- renderStyle as _renderStyle,
- renderAttrs as _renderAttrs,
- renderAttr as _renderAttr,
- renderDynamicAttr as _renderDynamicAttr
-} from './helpers/renderAttrs'
-export { interpolate as _interpolate } from './helpers/interpolate'
-export { renderList as _renderList } from './helpers/renderList'
+ ssrRenderClass as _ssrRenderClass,
+ ssrRenderStyle as _ssrRenderStyle,
+ ssrRenderAttrs as _ssrRenderAttrs,
+ ssrRenderAttr as _ssrRenderAttr,
+ ssrRenderDynamicAttr as _ssrRenderDynamicAttr
+} from './helpers/ssrRenderAttrs'
+export { ssrInterpolate as _ssrInterpolate } from './helpers/ssrInterpolate'
+export { ssrRenderList as _ssrRenderList } from './helpers/ssrRenderList'
// v-model helpers
export {
- looseEqual as _looseEqual,
- looseContain as _looseContain,
- renderDynamicModel as _renderDynamicModel,
- getDynamicModelProps as _getDynamicModelProps
-} from './helpers/vModelHelpers'
+ ssrLooseEqual as _ssrLooseEqual,
+ ssrLooseContain as _ssrLooseContain,
+ ssrRenderDynamicModel as _ssrRenderDynamicModel,
+ ssrGetDynamicModelProps as _ssrGetDynamicModelProps
+} from './helpers/ssrVModelHelpers'
isVoidTag,
escapeHtml
} from '@vue/shared'
-import { renderAttrs } from './helpers/renderAttrs'
-import { SSRSlots } from './helpers/renderSlot'
+import { ssrRenderAttrs } from './helpers/ssrRenderAttrs'
+import { SSRSlots } from './helpers/ssrRenderSlot'
const {
isVNode,
// TODO directives
if (props !== null) {
- openTag += renderAttrs(props, tag)
+ openTag += ssrRenderAttrs(props, tag)
}
if (scopeId !== null) {