You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1267 lines
55 KiB
1267 lines
55 KiB
2 years ago
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
|
||
|
var compilerDom = require('@vue/compiler-dom');
|
||
|
var shared = require('@vue/shared');
|
||
|
|
||
|
const SSR_INTERPOLATE = Symbol(`ssrInterpolate`);
|
||
|
const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`);
|
||
|
const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`);
|
||
|
const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`);
|
||
|
const SSR_RENDER_SLOT_INNER = Symbol(`ssrRenderSlotInner`);
|
||
|
const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`);
|
||
|
const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`);
|
||
|
const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`);
|
||
|
const SSR_RENDER_ATTR = Symbol(`ssrRenderAttr`);
|
||
|
const SSR_RENDER_DYNAMIC_ATTR = Symbol(`ssrRenderDynamicAttr`);
|
||
|
const SSR_RENDER_LIST = Symbol(`ssrRenderList`);
|
||
|
const SSR_INCLUDE_BOOLEAN_ATTR = Symbol(`ssrIncludeBooleanAttr`);
|
||
|
const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`);
|
||
|
const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`);
|
||
|
const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`);
|
||
|
const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`);
|
||
|
const SSR_RENDER_TELEPORT = Symbol(`ssrRenderTeleport`);
|
||
|
const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`);
|
||
|
const SSR_GET_DIRECTIVE_PROPS = Symbol(`ssrGetDirectiveProps`);
|
||
|
const ssrHelpers = {
|
||
|
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
||
|
[SSR_RENDER_VNODE]: `ssrRenderVNode`,
|
||
|
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
|
||
|
[SSR_RENDER_SLOT]: `ssrRenderSlot`,
|
||
|
[SSR_RENDER_SLOT_INNER]: `ssrRenderSlotInner`,
|
||
|
[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_INCLUDE_BOOLEAN_ATTR]: `ssrIncludeBooleanAttr`,
|
||
|
[SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
|
||
|
[SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
|
||
|
[SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
|
||
|
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`,
|
||
|
[SSR_RENDER_TELEPORT]: `ssrRenderTeleport`,
|
||
|
[SSR_RENDER_SUSPENSE]: `ssrRenderSuspense`,
|
||
|
[SSR_GET_DIRECTIVE_PROPS]: `ssrGetDirectiveProps`
|
||
|
};
|
||
|
// Note: these are helpers imported from @vue/server-renderer
|
||
|
// make sure the names match!
|
||
|
compilerDom.registerRuntimeHelpers(ssrHelpers);
|
||
|
|
||
|
// Plugin for the first transform pass, which simply constructs the AST node
|
||
|
const ssrTransformIf = compilerDom.createStructuralDirectiveTransform(/^(if|else|else-if)$/, compilerDom.processIf);
|
||
|
// This is called during the 2nd transform pass to construct the SSR-specific
|
||
|
// codegen nodes.
|
||
|
function ssrProcessIf(node, context, disableNestedFragments = false) {
|
||
|
const [rootBranch] = node.branches;
|
||
|
const ifStatement = compilerDom.createIfStatement(rootBranch.condition, processIfBranch(rootBranch, context, disableNestedFragments));
|
||
|
context.pushStatement(ifStatement);
|
||
|
let currentIf = ifStatement;
|
||
|
for (let i = 1; i < node.branches.length; i++) {
|
||
|
const branch = node.branches[i];
|
||
|
const branchBlockStatement = processIfBranch(branch, context, disableNestedFragments);
|
||
|
if (branch.condition) {
|
||
|
// else-if
|
||
|
currentIf = currentIf.alternate = compilerDom.createIfStatement(branch.condition, branchBlockStatement);
|
||
|
}
|
||
|
else {
|
||
|
// else
|
||
|
currentIf.alternate = branchBlockStatement;
|
||
|
}
|
||
|
}
|
||
|
if (!currentIf.alternate) {
|
||
|
currentIf.alternate = compilerDom.createBlockStatement([
|
||
|
compilerDom.createCallExpression(`_push`, ['`<!---->`'])
|
||
|
]);
|
||
|
}
|
||
|
}
|
||
|
function processIfBranch(branch, context, disableNestedFragments = false) {
|
||
|
const { children } = branch;
|
||
|
const needFragmentWrapper = !disableNestedFragments &&
|
||
|
(children.length !== 1 || children[0].type !== 1 /* ELEMENT */) &&
|
||
|
// optimize away nested fragments when the only child is a ForNode
|
||
|
!(children.length === 1 && children[0].type === 11 /* FOR */);
|
||
|
return processChildrenAsStatement(branch, context, needFragmentWrapper);
|
||
|
}
|
||
|
|
||
|
// Plugin for the first transform pass, which simply constructs the AST node
|
||
|
const ssrTransformFor = compilerDom.createStructuralDirectiveTransform('for', compilerDom.processFor);
|
||
|
// This is called during the 2nd transform pass to construct the SSR-specific
|
||
|
// codegen nodes.
|
||
|
function ssrProcessFor(node, context, disableNestedFragments = false) {
|
||
|
const needFragmentWrapper = !disableNestedFragments &&
|
||
|
(node.children.length !== 1 || node.children[0].type !== 1 /* ELEMENT */);
|
||
|
const renderLoop = compilerDom.createFunctionExpression(compilerDom.createForLoopParams(node.parseResult));
|
||
|
renderLoop.body = processChildrenAsStatement(node, context, needFragmentWrapper);
|
||
|
// v-for always renders a fragment unless explicitly disabled
|
||
|
if (!disableNestedFragments) {
|
||
|
context.pushStringPart(`<!--[-->`);
|
||
|
}
|
||
|
context.pushStatement(compilerDom.createCallExpression(context.helper(SSR_RENDER_LIST), [
|
||
|
node.source,
|
||
|
renderLoop
|
||
|
]));
|
||
|
if (!disableNestedFragments) {
|
||
|
context.pushStringPart(`<!--]-->`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ssrTransformSlotOutlet = (node, context) => {
|
||
|
if (compilerDom.isSlotOutlet(node)) {
|
||
|
const { slotName, slotProps } = compilerDom.processSlotOutlet(node, context);
|
||
|
const args = [
|
||
|
`_ctx.$slots`,
|
||
|
slotName,
|
||
|
slotProps || `{}`,
|
||
|
// fallback content placeholder. will be replaced in the process phase
|
||
|
`null`,
|
||
|
`_push`,
|
||
|
`_parent`
|
||
|
];
|
||
|
// inject slot scope id if current template uses :slotted
|
||
|
if (context.scopeId && context.slotted !== false) {
|
||
|
args.push(`"${context.scopeId}-s"`);
|
||
|
}
|
||
|
let method = SSR_RENDER_SLOT;
|
||
|
// #3989
|
||
|
// check if this is a single slot inside a transition wrapper - since
|
||
|
// transition will unwrap the slot fragment into a single vnode at runtime,
|
||
|
// we need to avoid rendering the slot as a fragment.
|
||
|
const parent = context.parent;
|
||
|
if (parent &&
|
||
|
parent.type === 1 /* ELEMENT */ &&
|
||
|
parent.tagType === 1 /* COMPONENT */ &&
|
||
|
compilerDom.resolveComponentType(parent, context, true) === compilerDom.TRANSITION &&
|
||
|
parent.children.filter(c => c.type === 1 /* ELEMENT */).length === 1) {
|
||
|
method = SSR_RENDER_SLOT_INNER;
|
||
|
if (!(context.scopeId && context.slotted !== false)) {
|
||
|
args.push('null');
|
||
|
}
|
||
|
args.push('true');
|
||
|
}
|
||
|
node.ssrCodegenNode = compilerDom.createCallExpression(context.helper(method), args);
|
||
|
}
|
||
|
};
|
||
|
function ssrProcessSlotOutlet(node, context) {
|
||
|
const renderCall = node.ssrCodegenNode;
|
||
|
// has fallback content
|
||
|
if (node.children.length) {
|
||
|
const fallbackRenderFn = compilerDom.createFunctionExpression([]);
|
||
|
fallbackRenderFn.body = processChildrenAsStatement(node, context);
|
||
|
// _renderSlot(slots, name, props, fallback, ...)
|
||
|
renderCall.arguments[3] = fallbackRenderFn;
|
||
|
}
|
||
|
// Forwarded <slot/>. Merge slot scope ids
|
||
|
if (context.withSlotScopeId) {
|
||
|
const slotScopeId = renderCall.arguments[6];
|
||
|
renderCall.arguments[6] = slotScopeId
|
||
|
? `${slotScopeId} + _scopeId`
|
||
|
: `_scopeId`;
|
||
|
}
|
||
|
context.pushStatement(node.ssrCodegenNode);
|
||
|
}
|
||
|
|
||
|
function createSSRCompilerError(code, loc) {
|
||
|
return compilerDom.createCompilerError(code, loc, SSRErrorMessages);
|
||
|
}
|
||
|
const SSRErrorMessages = {
|
||
|
[61 /* X_SSR_UNSAFE_ATTR_NAME */]: `Unsafe attribute name for SSR.`,
|
||
|
[62 /* X_SSR_NO_TELEPORT_TARGET */]: `Missing the 'to' prop on teleport element.`,
|
||
|
[63 /* X_SSR_INVALID_AST_NODE */]: `Invalid AST node during SSR transform.`
|
||
|
};
|
||
|
|
||
|
// Note: this is a 2nd-pass codegen transform.
|
||
|
function ssrProcessTeleport(node, context) {
|
||
|
const targetProp = compilerDom.findProp(node, 'to');
|
||
|
if (!targetProp) {
|
||
|
context.onError(createSSRCompilerError(62 /* X_SSR_NO_TELEPORT_TARGET */, node.loc));
|
||
|
return;
|
||
|
}
|
||
|
let target;
|
||
|
if (targetProp.type === 6 /* ATTRIBUTE */) {
|
||
|
target =
|
||
|
targetProp.value && compilerDom.createSimpleExpression(targetProp.value.content, true);
|
||
|
}
|
||
|
else {
|
||
|
target = targetProp.exp;
|
||
|
}
|
||
|
if (!target) {
|
||
|
context.onError(createSSRCompilerError(62 /* X_SSR_NO_TELEPORT_TARGET */, targetProp.loc));
|
||
|
return;
|
||
|
}
|
||
|
const disabledProp = compilerDom.findProp(node, 'disabled', false, true /* allow empty */);
|
||
|
const disabled = disabledProp
|
||
|
? disabledProp.type === 6 /* ATTRIBUTE */
|
||
|
? `true`
|
||
|
: disabledProp.exp || `false`
|
||
|
: `false`;
|
||
|
const contentRenderFn = compilerDom.createFunctionExpression([`_push`], undefined, // Body is added later
|
||
|
true, // newline
|
||
|
false, // isSlot
|
||
|
node.loc);
|
||
|
contentRenderFn.body = processChildrenAsStatement(node, context);
|
||
|
context.pushStatement(compilerDom.createCallExpression(context.helper(SSR_RENDER_TELEPORT), [
|
||
|
`_push`,
|
||
|
contentRenderFn,
|
||
|
target,
|
||
|
disabled,
|
||
|
`_parent`
|
||
|
]));
|
||
|
}
|
||
|
|
||
|
const wipMap = new WeakMap();
|
||
|
// phase 1
|
||
|
function ssrTransformSuspense(node, context) {
|
||
|
return () => {
|
||
|
if (node.children.length) {
|
||
|
const wipEntry = {
|
||
|
slotsExp: null,
|
||
|
wipSlots: []
|
||
|
};
|
||
|
wipMap.set(node, wipEntry);
|
||
|
wipEntry.slotsExp = compilerDom.buildSlots(node, context, (_props, children, loc) => {
|
||
|
const fn = compilerDom.createFunctionExpression([], undefined, // no return, assign body later
|
||
|
true, // newline
|
||
|
false, // suspense slots are not treated as normal slots
|
||
|
loc);
|
||
|
wipEntry.wipSlots.push({
|
||
|
fn,
|
||
|
children
|
||
|
});
|
||
|
return fn;
|
||
|
}).slots;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
// phase 2
|
||
|
function ssrProcessSuspense(node, context) {
|
||
|
// complete wip slots with ssr code
|
||
|
const wipEntry = wipMap.get(node);
|
||
|
if (!wipEntry) {
|
||
|
return;
|
||
|
}
|
||
|
const { slotsExp, wipSlots } = wipEntry;
|
||
|
for (let i = 0; i < wipSlots.length; i++) {
|
||
|
const slot = wipSlots[i];
|
||
|
slot.fn.body = processChildrenAsStatement(slot, context);
|
||
|
}
|
||
|
// _push(ssrRenderSuspense(slots))
|
||
|
context.pushStatement(compilerDom.createCallExpression(context.helper(SSR_RENDER_SUSPENSE), [
|
||
|
`_push`,
|
||
|
slotsExp
|
||
|
]));
|
||
|
}
|
||
|
|
||
|
// for directives with children overwrite (e.g. v-html & v-text), we need to
|
||
|
// store the raw children so that they can be added in the 2nd pass.
|
||
|
const rawChildrenMap = new WeakMap();
|
||
|
const ssrTransformElement = (node, context) => {
|
||
|
if (node.type !== 1 /* ELEMENT */ ||
|
||
|
node.tagType !== 0 /* ELEMENT */) {
|
||
|
return;
|
||
|
}
|
||
|
return function ssrPostTransformElement() {
|
||
|
// element
|
||
|
// generate the template literal representing the open tag.
|
||
|
const openTag = [`<${node.tag}`];
|
||
|
// some tags need to be passed to runtime for special checks
|
||
|
const needTagForRuntime = node.tag === 'textarea' || node.tag.indexOf('-') > 0;
|
||
|
// v-bind="obj", v-bind:[key] and custom directives can potentially
|
||
|
// overwrite other static attrs and can affect final rendering result,
|
||
|
// so when they are present we need to bail out to full `renderAttrs`
|
||
|
const hasDynamicVBind = compilerDom.hasDynamicKeyVBind(node);
|
||
|
const hasCustomDir = node.props.some(p => p.type === 7 /* DIRECTIVE */ && !shared.isBuiltInDirective(p.name));
|
||
|
const needMergeProps = hasDynamicVBind || hasCustomDir;
|
||
|
if (needMergeProps) {
|
||
|
const { props, directives } = compilerDom.buildProps(node, context, node.props, false /* isComponent */, false /* isDynamicComponent */, true /* ssr */);
|
||
|
if (props || directives.length) {
|
||
|
const mergedProps = buildSSRProps(props, directives, context);
|
||
|
const propsExp = compilerDom.createCallExpression(context.helper(SSR_RENDER_ATTRS), [mergedProps]);
|
||
|
if (node.tag === 'textarea') {
|
||
|
const existingText = node.children[0];
|
||
|
// If interpolation, this is dynamic <textarea> content, potentially
|
||
|
// injected by v-model and takes higher priority than v-bind value
|
||
|
if (!existingText || existingText.type !== 5 /* INTERPOLATION */) {
|
||
|
// <textarea> with dynamic v-bind. We don't know if the final props
|
||
|
// will contain .value, so we will have to do something special:
|
||
|
// assign the merged props to a temp variable, and check whether
|
||
|
// it contains value (if yes, render is as children).
|
||
|
const tempId = `_temp${context.temps++}`;
|
||
|
propsExp.arguments = [
|
||
|
compilerDom.createAssignmentExpression(compilerDom.createSimpleExpression(tempId, false), mergedProps)
|
||
|
];
|
||
|
rawChildrenMap.set(node, compilerDom.createCallExpression(context.helper(SSR_INTERPOLATE), [
|
||
|
compilerDom.createConditionalExpression(compilerDom.createSimpleExpression(`"value" in ${tempId}`, false), compilerDom.createSimpleExpression(`${tempId}.value`, false), compilerDom.createSimpleExpression(existingText ? existingText.content : ``, true), false)
|
||
|
]));
|
||
|
}
|
||
|
}
|
||
|
else if (node.tag === 'input') {
|
||
|
// <input v-bind="obj" v-model>
|
||
|
// we need to determine the props to render for the dynamic v-model
|
||
|
// and merge it with the v-bind expression.
|
||
|
const vModel = findVModel(node);
|
||
|
if (vModel) {
|
||
|
// 1. save the props (san v-model) in a temp variable
|
||
|
const tempId = `_temp${context.temps++}`;
|
||
|
const tempExp = compilerDom.createSimpleExpression(tempId, false);
|
||
|
propsExp.arguments = [
|
||
|
compilerDom.createSequenceExpression([
|
||
|
compilerDom.createAssignmentExpression(tempExp, mergedProps),
|
||
|
compilerDom.createCallExpression(context.helper(compilerDom.MERGE_PROPS), [
|
||
|
tempExp,
|
||
|
compilerDom.createCallExpression(context.helper(SSR_GET_DYNAMIC_MODEL_PROPS), [
|
||
|
tempExp,
|
||
|
vModel.exp // model
|
||
|
])
|
||
|
])
|
||
|
])
|
||
|
];
|
||
|
}
|
||
|
}
|
||
|
if (needTagForRuntime) {
|
||
|
propsExp.arguments.push(`"${node.tag}"`);
|
||
|
}
|
||
|
openTag.push(propsExp);
|
||
|
}
|
||
|
}
|
||
|
// book keeping static/dynamic class merging.
|
||
|
let dynamicClassBinding = undefined;
|
||
|
let staticClassBinding = undefined;
|
||
|
// all style bindings are converted to dynamic by transformStyle.
|
||
|
// but we need to make sure to merge them.
|
||
|
let dynamicStyleBinding = undefined;
|
||
|
for (let i = 0; i < node.props.length; i++) {
|
||
|
const prop = node.props[i];
|
||
|
// ignore true-value/false-value on input
|
||
|
if (node.tag === 'input' && isTrueFalseValue(prop)) {
|
||
|
continue;
|
||
|
}
|
||
|
// special cases with children override
|
||
|
if (prop.type === 7 /* DIRECTIVE */) {
|
||
|
if (prop.name === 'html' && prop.exp) {
|
||
|
rawChildrenMap.set(node, prop.exp);
|
||
|
}
|
||
|
else if (prop.name === 'text' && prop.exp) {
|
||
|
node.children = [compilerDom.createInterpolation(prop.exp, prop.loc)];
|
||
|
}
|
||
|
else if (prop.name === 'slot') {
|
||
|
context.onError(compilerDom.createCompilerError(40 /* X_V_SLOT_MISPLACED */, prop.loc));
|
||
|
}
|
||
|
else if (isTextareaWithValue(node, prop) && prop.exp) {
|
||
|
if (!needMergeProps) {
|
||
|
node.children = [compilerDom.createInterpolation(prop.exp, prop.loc)];
|
||
|
}
|
||
|
}
|
||
|
else if (!needMergeProps && prop.name !== 'on') {
|
||
|
// Directive transforms.
|
||
|
const directiveTransform = context.directiveTransforms[prop.name];
|
||
|
if (directiveTransform) {
|
||
|
const { props, ssrTagParts } = directiveTransform(prop, node, context);
|
||
|
if (ssrTagParts) {
|
||
|
openTag.push(...ssrTagParts);
|
||
|
}
|
||
|
for (let j = 0; j < props.length; j++) {
|
||
|
const { key, value } = props[j];
|
||
|
if (compilerDom.isStaticExp(key)) {
|
||
|
let attrName = key.content;
|
||
|
// static key attr
|
||
|
if (attrName === 'key' || attrName === 'ref') {
|
||
|
continue;
|
||
|
}
|
||
|
if (attrName === 'class') {
|
||
|
openTag.push(` class="`, (dynamicClassBinding = compilerDom.createCallExpression(context.helper(SSR_RENDER_CLASS), [value])), `"`);
|
||
|
}
|
||
|
else if (attrName === 'style') {
|
||
|
if (dynamicStyleBinding) {
|
||
|
// already has style binding, merge into it.
|
||
|
mergeCall(dynamicStyleBinding, value);
|
||
|
}
|
||
|
else {
|
||
|
openTag.push(` style="`, (dynamicStyleBinding = compilerDom.createCallExpression(context.helper(SSR_RENDER_STYLE), [value])), `"`);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
attrName =
|
||
|
node.tag.indexOf('-') > 0
|
||
|
? attrName // preserve raw name on custom elements
|
||
|
: shared.propsToAttrMap[attrName] || attrName.toLowerCase();
|
||
|
if (shared.isBooleanAttr(attrName)) {
|
||
|
openTag.push(compilerDom.createConditionalExpression(compilerDom.createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [value]), compilerDom.createSimpleExpression(' ' + attrName, true), compilerDom.createSimpleExpression('', true), false /* no newline */));
|
||
|
}
|
||
|
else if (shared.isSSRSafeAttrName(attrName)) {
|
||
|
openTag.push(compilerDom.createCallExpression(context.helper(SSR_RENDER_ATTR), [
|
||
|
key,
|
||
|
value
|
||
|
]));
|
||
|
}
|
||
|
else {
|
||
|
context.onError(createSSRCompilerError(61 /* X_SSR_UNSAFE_ATTR_NAME */, key.loc));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// dynamic key attr
|
||
|
// this branch is only encountered for custom directive
|
||
|
// transforms that returns properties with dynamic keys
|
||
|
const args = [key, value];
|
||
|
if (needTagForRuntime) {
|
||
|
args.push(`"${node.tag}"`);
|
||
|
}
|
||
|
openTag.push(compilerDom.createCallExpression(context.helper(SSR_RENDER_DYNAMIC_ATTR), args));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// special case: value on <textarea>
|
||
|
if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
|
||
|
rawChildrenMap.set(node, shared.escapeHtml(prop.value.content));
|
||
|
}
|
||
|
else if (!needMergeProps) {
|
||
|
if (prop.name === 'key' || prop.name === 'ref') {
|
||
|
continue;
|
||
|
}
|
||
|
// static prop
|
||
|
if (prop.name === 'class' && prop.value) {
|
||
|
staticClassBinding = JSON.stringify(prop.value.content);
|
||
|
}
|
||
|
openTag.push(` ${prop.name}` +
|
||
|
(prop.value ? `="${shared.escapeHtml(prop.value.content)}"` : ``));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// handle co-existence of dynamic + static class bindings
|
||
|
if (dynamicClassBinding && staticClassBinding) {
|
||
|
mergeCall(dynamicClassBinding, staticClassBinding);
|
||
|
removeStaticBinding(openTag, 'class');
|
||
|
}
|
||
|
if (context.scopeId) {
|
||
|
openTag.push(` ${context.scopeId}`);
|
||
|
}
|
||
|
node.ssrCodegenNode = compilerDom.createTemplateLiteral(openTag);
|
||
|
};
|
||
|
};
|
||
|
function buildSSRProps(props, directives, context) {
|
||
|
let mergePropsArgs = [];
|
||
|
if (props) {
|
||
|
if (props.type === 14 /* JS_CALL_EXPRESSION */) {
|
||
|
// already a mergeProps call
|
||
|
mergePropsArgs = props.arguments;
|
||
|
}
|
||
|
else {
|
||
|
mergePropsArgs.push(props);
|
||
|
}
|
||
|
}
|
||
|
if (directives.length) {
|
||
|
for (const dir of directives) {
|
||
|
mergePropsArgs.push(compilerDom.createCallExpression(context.helper(SSR_GET_DIRECTIVE_PROPS), [
|
||
|
`_ctx`,
|
||
|
...compilerDom.buildDirectiveArgs(dir, context).elements
|
||
|
]));
|
||
|
}
|
||
|
}
|
||
|
return mergePropsArgs.length > 1
|
||
|
? compilerDom.createCallExpression(context.helper(compilerDom.MERGE_PROPS), mergePropsArgs)
|
||
|
: mergePropsArgs[0];
|
||
|
}
|
||
|
function isTrueFalseValue(prop) {
|
||
|
if (prop.type === 7 /* DIRECTIVE */) {
|
||
|
return (prop.name === 'bind' &&
|
||
|
prop.arg &&
|
||
|
compilerDom.isStaticExp(prop.arg) &&
|
||
|
(prop.arg.content === 'true-value' || prop.arg.content === 'false-value'));
|
||
|
}
|
||
|
else {
|
||
|
return prop.name === 'true-value' || prop.name === 'false-value';
|
||
|
}
|
||
|
}
|
||
|
function isTextareaWithValue(node, prop) {
|
||
|
return !!(node.tag === 'textarea' &&
|
||
|
prop.name === 'bind' &&
|
||
|
compilerDom.isStaticArgOf(prop.arg, 'value'));
|
||
|
}
|
||
|
function mergeCall(call, arg) {
|
||
|
const existing = call.arguments[0];
|
||
|
if (existing.type === 17 /* JS_ARRAY_EXPRESSION */) {
|
||
|
existing.elements.push(arg);
|
||
|
}
|
||
|
else {
|
||
|
call.arguments[0] = compilerDom.createArrayExpression([existing, arg]);
|
||
|
}
|
||
|
}
|
||
|
function removeStaticBinding(tag, binding) {
|
||
|
const regExp = new RegExp(`^ ${binding}=".+"$`);
|
||
|
const i = tag.findIndex(e => typeof e === 'string' && regExp.test(e));
|
||
|
if (i > -1) {
|
||
|
tag.splice(i, 1);
|
||
|
}
|
||
|
}
|
||
|
function findVModel(node) {
|
||
|
return node.props.find(p => p.type === 7 /* DIRECTIVE */ && p.name === 'model' && p.exp);
|
||
|
}
|
||
|
function ssrProcessElement(node, context) {
|
||
|
const isVoidTag = context.options.isVoidTag || shared.NO;
|
||
|
const elementsToAdd = node.ssrCodegenNode.elements;
|
||
|
for (let j = 0; j < elementsToAdd.length; j++) {
|
||
|
context.pushStringPart(elementsToAdd[j]);
|
||
|
}
|
||
|
// Handle slot scopeId
|
||
|
if (context.withSlotScopeId) {
|
||
|
context.pushStringPart(compilerDom.createSimpleExpression(`_scopeId`, false));
|
||
|
}
|
||
|
// close open tag
|
||
|
context.pushStringPart(`>`);
|
||
|
const rawChildren = rawChildrenMap.get(node);
|
||
|
if (rawChildren) {
|
||
|
context.pushStringPart(rawChildren);
|
||
|
}
|
||
|
else if (node.children.length) {
|
||
|
processChildren(node, context);
|
||
|
}
|
||
|
if (!isVoidTag(node.tag)) {
|
||
|
// push closing tag
|
||
|
context.pushStringPart(`</${node.tag}>`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const wipMap$1 = new WeakMap();
|
||
|
// phase 1: build props
|
||
|
function ssrTransformTransitionGroup(node, context) {
|
||
|
return () => {
|
||
|
const tag = compilerDom.findProp(node, 'tag');
|
||
|
if (tag) {
|
||
|
const otherProps = node.props.filter(p => p !== tag);
|
||
|
const { props, directives } = compilerDom.buildProps(node, context, otherProps, true, /* isComponent */ false, /* isDynamicComponent */ true /* ssr (skip event listeners) */);
|
||
|
let propsExp = null;
|
||
|
if (props || directives.length) {
|
||
|
propsExp = compilerDom.createCallExpression(context.helper(SSR_RENDER_ATTRS), [
|
||
|
buildSSRProps(props, directives, context)
|
||
|
]);
|
||
|
}
|
||
|
wipMap$1.set(node, {
|
||
|
tag,
|
||
|
propsExp
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
// phase 2: process children
|
||
|
function ssrProcessTransitionGroup(node, context) {
|
||
|
const entry = wipMap$1.get(node);
|
||
|
if (entry) {
|
||
|
const { tag, propsExp } = entry;
|
||
|
if (tag.type === 7 /* DIRECTIVE */) {
|
||
|
// dynamic :tag
|
||
|
context.pushStringPart(`<`);
|
||
|
context.pushStringPart(tag.exp);
|
||
|
if (propsExp) {
|
||
|
context.pushStringPart(propsExp);
|
||
|
}
|
||
|
context.pushStringPart(`>`);
|
||
|
processChildren(node, context, false,
|
||
|
/**
|
||
|
* TransitionGroup has the special runtime behavior of flattening and
|
||
|
* concatenating all children into a single fragment (in order for them to
|
||
|
* be patched using the same key map) so we need to account for that here
|
||
|
* by disabling nested fragment wrappers from being generated.
|
||
|
*/
|
||
|
true);
|
||
|
context.pushStringPart(`</`);
|
||
|
context.pushStringPart(tag.exp);
|
||
|
context.pushStringPart(`>`);
|
||
|
}
|
||
|
else {
|
||
|
// static tag
|
||
|
context.pushStringPart(`<${tag.value.content}`);
|
||
|
if (propsExp) {
|
||
|
context.pushStringPart(propsExp);
|
||
|
}
|
||
|
context.pushStringPart(`>`);
|
||
|
processChildren(node, context, false, true);
|
||
|
context.pushStringPart(`</${tag.value.content}>`);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// fragment
|
||
|
processChildren(node, context, true, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We need to construct the slot functions in the 1st pass to ensure proper
|
||
|
// scope tracking, but the children of each slot cannot be processed until
|
||
|
// the 2nd pass, so we store the WIP slot functions in a weakMap during the 1st
|
||
|
// pass and complete them in the 2nd pass.
|
||
|
const wipMap$2 = new WeakMap();
|
||
|
const WIP_SLOT = Symbol();
|
||
|
const componentTypeMap = new WeakMap();
|
||
|
// ssr component transform is done in two phases:
|
||
|
// In phase 1. we use `buildSlot` to analyze the children of the component into
|
||
|
// WIP slot functions (it must be done in phase 1 because `buildSlot` relies on
|
||
|
// the core transform context).
|
||
|
// In phase 2. we convert the WIP slots from phase 1 into ssr-specific codegen
|
||
|
// nodes.
|
||
|
const ssrTransformComponent = (node, context) => {
|
||
|
if (node.type !== 1 /* ELEMENT */ ||
|
||
|
node.tagType !== 1 /* COMPONENT */) {
|
||
|
return;
|
||
|
}
|
||
|
const component = compilerDom.resolveComponentType(node, context, true /* ssr */);
|
||
|
const isDynamicComponent = shared.isObject(component) && component.callee === compilerDom.RESOLVE_DYNAMIC_COMPONENT;
|
||
|
componentTypeMap.set(node, component);
|
||
|
if (shared.isSymbol(component)) {
|
||
|
if (component === compilerDom.SUSPENSE) {
|
||
|
return ssrTransformSuspense(node, context);
|
||
|
}
|
||
|
if (component === compilerDom.TRANSITION_GROUP) {
|
||
|
return ssrTransformTransitionGroup(node, context);
|
||
|
}
|
||
|
return; // other built-in components: fallthrough
|
||
|
}
|
||
|
// Build the fallback vnode-based branch for the component's slots.
|
||
|
// We need to clone the node into a fresh copy and use the buildSlots' logic
|
||
|
// to get access to the children of each slot. We then compile them with
|
||
|
// a child transform pipeline using vnode-based transforms (instead of ssr-
|
||
|
// based ones), and save the result branch (a ReturnStatement) in an array.
|
||
|
// The branch is retrieved when processing slots again in ssr mode.
|
||
|
const vnodeBranches = [];
|
||
|
const clonedNode = clone(node);
|
||
|
return function ssrPostTransformComponent() {
|
||
|
// Using the cloned node, build the normal VNode-based branches (for
|
||
|
// fallback in case the child is render-fn based). Store them in an array
|
||
|
// for later use.
|
||
|
if (clonedNode.children.length) {
|
||
|
compilerDom.buildSlots(clonedNode, context, (props, children) => {
|
||
|
vnodeBranches.push(createVNodeSlotBranch(props, children, context));
|
||
|
return compilerDom.createFunctionExpression(undefined);
|
||
|
});
|
||
|
}
|
||
|
let propsExp = `null`;
|
||
|
if (node.props.length) {
|
||
|
// note we are not passing ssr: true here because for components, v-on
|
||
|
// handlers should still be passed
|
||
|
const { props, directives } = compilerDom.buildProps(node, context, undefined, true, isDynamicComponent);
|
||
|
if (props || directives.length) {
|
||
|
propsExp = buildSSRProps(props, directives, context);
|
||
|
}
|
||
|
}
|
||
|
const wipEntries = [];
|
||
|
wipMap$2.set(node, wipEntries);
|
||
|
const buildSSRSlotFn = (props, children, loc) => {
|
||
|
const fn = compilerDom.createFunctionExpression([props || `_`, `_push`, `_parent`, `_scopeId`], undefined, // no return, assign body later
|
||
|
true, // newline
|
||
|
true, // isSlot
|
||
|
loc);
|
||
|
wipEntries.push({
|
||
|
type: WIP_SLOT,
|
||
|
fn,
|
||
|
children,
|
||
|
// also collect the corresponding vnode branch built earlier
|
||
|
vnodeBranch: vnodeBranches[wipEntries.length]
|
||
|
});
|
||
|
return fn;
|
||
|
};
|
||
|
const slots = node.children.length
|
||
|
? compilerDom.buildSlots(node, context, buildSSRSlotFn).slots
|
||
|
: `null`;
|
||
|
if (typeof component !== 'string') {
|
||
|
// dynamic component that resolved to a `resolveDynamicComponent` call
|
||
|
// expression - since the resolved result may be a plain element (string)
|
||
|
// or a VNode, handle it with `renderVNode`.
|
||
|
node.ssrCodegenNode = compilerDom.createCallExpression(context.helper(SSR_RENDER_VNODE), [
|
||
|
`_push`,
|
||
|
compilerDom.createCallExpression(context.helper(compilerDom.CREATE_VNODE), [
|
||
|
component,
|
||
|
propsExp,
|
||
|
slots
|
||
|
]),
|
||
|
`_parent`
|
||
|
]);
|
||
|
}
|
||
|
else {
|
||
|
node.ssrCodegenNode = compilerDom.createCallExpression(context.helper(SSR_RENDER_COMPONENT), [component, propsExp, slots, `_parent`]);
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
function ssrProcessComponent(node, context, parent) {
|
||
|
const component = componentTypeMap.get(node);
|
||
|
if (!node.ssrCodegenNode) {
|
||
|
// this is a built-in component that fell-through.
|
||
|
if (component === compilerDom.TELEPORT) {
|
||
|
return ssrProcessTeleport(node, context);
|
||
|
}
|
||
|
else if (component === compilerDom.SUSPENSE) {
|
||
|
return ssrProcessSuspense(node, context);
|
||
|
}
|
||
|
else if (component === compilerDom.TRANSITION_GROUP) {
|
||
|
return ssrProcessTransitionGroup(node, context);
|
||
|
}
|
||
|
else {
|
||
|
// real fall-through: Transition / KeepAlive
|
||
|
// just render its children.
|
||
|
// #5352: if is at root level of a slot, push an empty string.
|
||
|
// this does not affect the final output, but avoids all-comment slot
|
||
|
// content of being treated as empty by ssrRenderSlot().
|
||
|
if (parent.type === WIP_SLOT) {
|
||
|
context.pushStringPart(``);
|
||
|
}
|
||
|
// #5351: filter out comment children inside transition
|
||
|
if (component === compilerDom.TRANSITION) {
|
||
|
node.children = node.children.filter(c => c.type !== 3 /* COMMENT */);
|
||
|
}
|
||
|
processChildren(node, context);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// finish up slot function expressions from the 1st pass.
|
||
|
const wipEntries = wipMap$2.get(node) || [];
|
||
|
for (let i = 0; i < wipEntries.length; i++) {
|
||
|
const { fn, vnodeBranch } = wipEntries[i];
|
||
|
// For each slot, we generate two branches: one SSR-optimized branch and
|
||
|
// one normal vnode-based branch. The branches are taken based on the
|
||
|
// presence of the 2nd `_push` argument (which is only present if the slot
|
||
|
// is called by `_ssrRenderSlot`.
|
||
|
fn.body = compilerDom.createIfStatement(compilerDom.createSimpleExpression(`_push`, false), processChildrenAsStatement(wipEntries[i], context, false, true /* withSlotScopeId */), vnodeBranch);
|
||
|
}
|
||
|
// component is inside a slot, inherit slot scope Id
|
||
|
if (context.withSlotScopeId) {
|
||
|
node.ssrCodegenNode.arguments.push(`_scopeId`);
|
||
|
}
|
||
|
if (typeof component === 'string') {
|
||
|
// static component
|
||
|
context.pushStatement(compilerDom.createCallExpression(`_push`, [node.ssrCodegenNode]));
|
||
|
}
|
||
|
else {
|
||
|
// dynamic component (`resolveDynamicComponent` call)
|
||
|
// the codegen node is a `renderVNode` call
|
||
|
context.pushStatement(node.ssrCodegenNode);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const rawOptionsMap = new WeakMap();
|
||
|
const [baseNodeTransforms, baseDirectiveTransforms] = compilerDom.getBaseTransformPreset(true);
|
||
|
const vnodeNodeTransforms = [...baseNodeTransforms, ...compilerDom.DOMNodeTransforms];
|
||
|
const vnodeDirectiveTransforms = {
|
||
|
...baseDirectiveTransforms,
|
||
|
...compilerDom.DOMDirectiveTransforms
|
||
|
};
|
||
|
function createVNodeSlotBranch(props, children, parentContext) {
|
||
|
// apply a sub-transform using vnode-based transforms.
|
||
|
const rawOptions = rawOptionsMap.get(parentContext.root);
|
||
|
const subOptions = {
|
||
|
...rawOptions,
|
||
|
// overwrite with vnode-based transforms
|
||
|
nodeTransforms: [
|
||
|
...vnodeNodeTransforms,
|
||
|
...(rawOptions.nodeTransforms || [])
|
||
|
],
|
||
|
directiveTransforms: {
|
||
|
...vnodeDirectiveTransforms,
|
||
|
...(rawOptions.directiveTransforms || {})
|
||
|
}
|
||
|
};
|
||
|
// wrap the children with a wrapper template for proper children treatment.
|
||
|
const wrapperNode = {
|
||
|
type: 1 /* ELEMENT */,
|
||
|
ns: 0 /* HTML */,
|
||
|
tag: 'template',
|
||
|
tagType: 3 /* TEMPLATE */,
|
||
|
isSelfClosing: false,
|
||
|
// important: provide v-slot="props" on the wrapper for proper
|
||
|
// scope analysis
|
||
|
props: [
|
||
|
{
|
||
|
type: 7 /* DIRECTIVE */,
|
||
|
name: 'slot',
|
||
|
exp: props,
|
||
|
arg: undefined,
|
||
|
modifiers: [],
|
||
|
loc: compilerDom.locStub
|
||
|
}
|
||
|
],
|
||
|
children,
|
||
|
loc: compilerDom.locStub,
|
||
|
codegenNode: undefined
|
||
|
};
|
||
|
subTransform(wrapperNode, subOptions, parentContext);
|
||
|
return compilerDom.createReturnStatement(children);
|
||
|
}
|
||
|
function subTransform(node, options, parentContext) {
|
||
|
const childRoot = compilerDom.createRoot([node]);
|
||
|
const childContext = compilerDom.createTransformContext(childRoot, options);
|
||
|
// this sub transform is for vnode fallback branch so it should be handled
|
||
|
// like normal render functions
|
||
|
childContext.ssr = false;
|
||
|
// inherit parent scope analysis state
|
||
|
childContext.scopes = { ...parentContext.scopes };
|
||
|
childContext.identifiers = { ...parentContext.identifiers };
|
||
|
childContext.imports = parentContext.imports;
|
||
|
// traverse
|
||
|
compilerDom.traverseNode(childRoot, childContext);
|
||
|
['helpers', 'components', 'directives'].forEach(key => {
|
||
|
childContext[key].forEach((value, helperKey) => {
|
||
|
if (key === 'helpers') {
|
||
|
const parentCount = parentContext.helpers.get(helperKey);
|
||
|
if (parentCount === undefined) {
|
||
|
parentContext.helpers.set(helperKey, value);
|
||
|
}
|
||
|
else {
|
||
|
parentContext.helpers.set(helperKey, value + parentCount);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
parentContext[key].add(value);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
// imports/hoists are not merged because:
|
||
|
// - imports are only used for asset urls and should be consistent between
|
||
|
// node/client branches
|
||
|
// - hoists are not enabled for the client branch here
|
||
|
}
|
||
|
function clone(v) {
|
||
|
if (shared.isArray(v)) {
|
||
|
return v.map(clone);
|
||
|
}
|
||
|
else if (shared.isObject(v)) {
|
||
|
const res = {};
|
||
|
for (const key in v) {
|
||
|
res[key] = clone(v[key]);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
else {
|
||
|
return v;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Because SSR codegen output is completely different from client-side output
|
||
|
// (e.g. multiple elements can be concatenated into a single template literal
|
||
|
// instead of each getting a corresponding call), we need to apply an extra
|
||
|
// transform pass to convert the template AST into a fresh JS AST before
|
||
|
// passing it to codegen.
|
||
|
function ssrCodegenTransform(ast, options) {
|
||
|
const context = createSSRTransformContext(ast, options);
|
||
|
// inject SFC <style> CSS variables
|
||
|
// we do this instead of inlining the expression to ensure the vars are
|
||
|
// only resolved once per render
|
||
|
if (options.ssrCssVars) {
|
||
|
const varsExp = compilerDom.processExpression(compilerDom.createSimpleExpression(options.ssrCssVars, false), compilerDom.createTransformContext(compilerDom.createRoot([]), options));
|
||
|
context.body.push(compilerDom.createCompoundExpression([`const _cssVars = { style: `, varsExp, `}`]));
|
||
|
}
|
||
|
const isFragment = ast.children.length > 1 && ast.children.some(c => !compilerDom.isText(c));
|
||
|
processChildren(ast, context, isFragment);
|
||
|
ast.codegenNode = compilerDom.createBlockStatement(context.body);
|
||
|
// Finalize helpers.
|
||
|
// We need to separate helpers imported from 'vue' vs. '@vue/server-renderer'
|
||
|
ast.ssrHelpers = Array.from(new Set([...ast.helpers.filter(h => h in ssrHelpers), ...context.helpers]));
|
||
|
ast.helpers = ast.helpers.filter(h => !(h in ssrHelpers));
|
||
|
}
|
||
|
function createSSRTransformContext(root, options, helpers = new Set(), withSlotScopeId = false) {
|
||
|
const body = [];
|
||
|
let currentString = null;
|
||
|
return {
|
||
|
root,
|
||
|
options,
|
||
|
body,
|
||
|
helpers,
|
||
|
withSlotScopeId,
|
||
|
onError: options.onError ||
|
||
|
(e => {
|
||
|
throw e;
|
||
|
}),
|
||
|
helper(name) {
|
||
|
helpers.add(name);
|
||
|
return name;
|
||
|
},
|
||
|
pushStringPart(part) {
|
||
|
if (!currentString) {
|
||
|
const currentCall = compilerDom.createCallExpression(`_push`);
|
||
|
body.push(currentCall);
|
||
|
currentString = compilerDom.createTemplateLiteral([]);
|
||
|
currentCall.arguments.push(currentString);
|
||
|
}
|
||
|
const bufferedElements = currentString.elements;
|
||
|
const lastItem = bufferedElements[bufferedElements.length - 1];
|
||
|
if (shared.isString(part) && shared.isString(lastItem)) {
|
||
|
bufferedElements[bufferedElements.length - 1] += part;
|
||
|
}
|
||
|
else {
|
||
|
bufferedElements.push(part);
|
||
|
}
|
||
|
},
|
||
|
pushStatement(statement) {
|
||
|
// close current string
|
||
|
currentString = null;
|
||
|
body.push(statement);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function createChildContext(parent, withSlotScopeId = parent.withSlotScopeId) {
|
||
|
// ensure child inherits parent helpers
|
||
|
return createSSRTransformContext(parent.root, parent.options, parent.helpers, withSlotScopeId);
|
||
|
}
|
||
|
function processChildren(parent, context, asFragment = false, disableNestedFragments = false) {
|
||
|
if (asFragment) {
|
||
|
context.pushStringPart(`<!--[-->`);
|
||
|
}
|
||
|
const { children } = parent;
|
||
|
for (let i = 0; i < children.length; i++) {
|
||
|
const child = children[i];
|
||
|
switch (child.type) {
|
||
|
case 1 /* ELEMENT */:
|
||
|
switch (child.tagType) {
|
||
|
case 0 /* ELEMENT */:
|
||
|
ssrProcessElement(child, context);
|
||
|
break;
|
||
|
case 1 /* COMPONENT */:
|
||
|
ssrProcessComponent(child, context, parent);
|
||
|
break;
|
||
|
case 2 /* SLOT */:
|
||
|
ssrProcessSlotOutlet(child, context);
|
||
|
break;
|
||
|
case 3 /* TEMPLATE */:
|
||
|
// TODO
|
||
|
break;
|
||
|
default:
|
||
|
context.onError(createSSRCompilerError(63 /* X_SSR_INVALID_AST_NODE */, child.loc));
|
||
|
// make sure we exhaust all possible types
|
||
|
const exhaustiveCheck = child;
|
||
|
return exhaustiveCheck;
|
||
|
}
|
||
|
break;
|
||
|
case 2 /* TEXT */:
|
||
|
context.pushStringPart(shared.escapeHtml(child.content));
|
||
|
break;
|
||
|
case 3 /* COMMENT */:
|
||
|
// no need to escape comment here because the AST can only
|
||
|
// contain valid comments.
|
||
|
context.pushStringPart(`<!--${child.content}-->`);
|
||
|
break;
|
||
|
case 5 /* INTERPOLATION */:
|
||
|
context.pushStringPart(compilerDom.createCallExpression(context.helper(SSR_INTERPOLATE), [child.content]));
|
||
|
break;
|
||
|
case 9 /* IF */:
|
||
|
ssrProcessIf(child, context, disableNestedFragments);
|
||
|
break;
|
||
|
case 11 /* FOR */:
|
||
|
ssrProcessFor(child, context, disableNestedFragments);
|
||
|
break;
|
||
|
case 10 /* IF_BRANCH */:
|
||
|
// no-op - handled by ssrProcessIf
|
||
|
break;
|
||
|
case 12 /* TEXT_CALL */:
|
||
|
case 8 /* COMPOUND_EXPRESSION */:
|
||
|
// no-op - these two types can never appear as template child node since
|
||
|
// `transformText` is not used during SSR compile.
|
||
|
break;
|
||
|
default:
|
||
|
context.onError(createSSRCompilerError(63 /* X_SSR_INVALID_AST_NODE */, child.loc));
|
||
|
// make sure we exhaust all possible types
|
||
|
const exhaustiveCheck = child;
|
||
|
return exhaustiveCheck;
|
||
|
}
|
||
|
}
|
||
|
if (asFragment) {
|
||
|
context.pushStringPart(`<!--]-->`);
|
||
|
}
|
||
|
}
|
||
|
function processChildrenAsStatement(parent, parentContext, asFragment = false, withSlotScopeId = parentContext.withSlotScopeId) {
|
||
|
const childContext = createChildContext(parentContext, withSlotScopeId);
|
||
|
processChildren(parent, childContext, asFragment);
|
||
|
return compilerDom.createBlockStatement(childContext.body);
|
||
|
}
|
||
|
|
||
|
const ssrTransformModel = (dir, node, context) => {
|
||
|
const model = dir.exp;
|
||
|
function checkDuplicatedValue() {
|
||
|
const value = compilerDom.findProp(node, 'value');
|
||
|
if (value) {
|
||
|
context.onError(compilerDom.createDOMCompilerError(57 /* X_V_MODEL_UNNECESSARY_VALUE */, value.loc));
|
||
|
}
|
||
|
}
|
||
|
if (node.tagType === 0 /* ELEMENT */) {
|
||
|
const res = { props: [] };
|
||
|
const defaultProps = [
|
||
|
// default value binding for text type inputs
|
||
|
compilerDom.createObjectProperty(`value`, model)
|
||
|
];
|
||
|
if (node.tag === 'input') {
|
||
|
const type = compilerDom.findProp(node, 'type');
|
||
|
if (type) {
|
||
|
const value = findValueBinding(node);
|
||
|
if (type.type === 7 /* DIRECTIVE */) {
|
||
|
// dynamic type
|
||
|
res.ssrTagParts = [
|
||
|
compilerDom.createCallExpression(context.helper(SSR_RENDER_DYNAMIC_MODEL), [
|
||
|
type.exp,
|
||
|
model,
|
||
|
value
|
||
|
])
|
||
|
];
|
||
|
}
|
||
|
else if (type.value) {
|
||
|
// static type
|
||
|
switch (type.value.content) {
|
||
|
case 'radio':
|
||
|
res.props = [
|
||
|
compilerDom.createObjectProperty(`checked`, compilerDom.createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
||
|
model,
|
||
|
value
|
||
|
]))
|
||
|
];
|
||
|
break;
|
||
|
case 'checkbox':
|
||
|
const trueValueBinding = compilerDom.findProp(node, 'true-value');
|
||
|
if (trueValueBinding) {
|
||
|
const trueValue = trueValueBinding.type === 6 /* ATTRIBUTE */
|
||
|
? JSON.stringify(trueValueBinding.value.content)
|
||
|
: trueValueBinding.exp;
|
||
|
res.props = [
|
||
|
compilerDom.createObjectProperty(`checked`, compilerDom.createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
||
|
model,
|
||
|
trueValue
|
||
|
]))
|
||
|
];
|
||
|
}
|
||
|
else {
|
||
|
res.props = [
|
||
|
compilerDom.createObjectProperty(`checked`, compilerDom.createConditionalExpression(compilerDom.createCallExpression(`Array.isArray`, [model]), compilerDom.createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
|
||
|
model,
|
||
|
value
|
||
|
]), model))
|
||
|
];
|
||
|
}
|
||
|
break;
|
||
|
case 'file':
|
||
|
context.onError(compilerDom.createDOMCompilerError(56 /* X_V_MODEL_ON_FILE_INPUT_ELEMENT */, dir.loc));
|
||
|
break;
|
||
|
default:
|
||
|
checkDuplicatedValue();
|
||
|
res.props = defaultProps;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (compilerDom.hasDynamicKeyVBind(node)) ;
|
||
|
else {
|
||
|
// text type
|
||
|
checkDuplicatedValue();
|
||
|
res.props = defaultProps;
|
||
|
}
|
||
|
}
|
||
|
else if (node.tag === 'textarea') {
|
||
|
checkDuplicatedValue();
|
||
|
node.children = [compilerDom.createInterpolation(model, model.loc)];
|
||
|
}
|
||
|
else if (node.tag === 'select') ;
|
||
|
else {
|
||
|
context.onError(compilerDom.createDOMCompilerError(54 /* X_V_MODEL_ON_INVALID_ELEMENT */, dir.loc));
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
else {
|
||
|
// component v-model
|
||
|
return compilerDom.transformModel(dir, node, context);
|
||
|
}
|
||
|
};
|
||
|
function findValueBinding(node) {
|
||
|
const valueBinding = compilerDom.findProp(node, 'value');
|
||
|
return valueBinding
|
||
|
? valueBinding.type === 7 /* DIRECTIVE */
|
||
|
? valueBinding.exp
|
||
|
: compilerDom.createSimpleExpression(valueBinding.value.content, true)
|
||
|
: compilerDom.createSimpleExpression(`null`, false);
|
||
|
}
|
||
|
|
||
|
const ssrTransformShow = (dir, node, context) => {
|
||
|
if (!dir.exp) {
|
||
|
context.onError(compilerDom.createDOMCompilerError(58 /* X_V_SHOW_NO_EXPRESSION */));
|
||
|
}
|
||
|
return {
|
||
|
props: [
|
||
|
compilerDom.createObjectProperty(`style`, compilerDom.createConditionalExpression(dir.exp, compilerDom.createSimpleExpression(`null`, false), compilerDom.createObjectExpression([
|
||
|
compilerDom.createObjectProperty(`display`, compilerDom.createSimpleExpression(`none`, true))
|
||
|
]), false /* no newline */))
|
||
|
]
|
||
|
};
|
||
|
};
|
||
|
|
||
|
const filterChild = (node) => node.children.filter(n => n.type !== 3 /* COMMENT */);
|
||
|
const hasSingleChild = (node) => filterChild(node).length === 1;
|
||
|
const ssrInjectFallthroughAttrs = (node, context) => {
|
||
|
// _attrs is provided as a function argument.
|
||
|
// mark it as a known identifier so that it doesn't get prefixed by
|
||
|
// transformExpression.
|
||
|
if (node.type === 0 /* ROOT */) {
|
||
|
context.identifiers._attrs = 1;
|
||
|
}
|
||
|
if (node.type === 1 /* ELEMENT */ &&
|
||
|
node.tagType === 1 /* COMPONENT */ &&
|
||
|
(compilerDom.isBuiltInType(node.tag, 'Transition') ||
|
||
|
compilerDom.isBuiltInType(node.tag, 'KeepAlive'))) {
|
||
|
const rootChildren = filterChild(context.root);
|
||
|
if (rootChildren.length === 1 && rootChildren[0] === node) {
|
||
|
if (hasSingleChild(node)) {
|
||
|
injectFallthroughAttrs(node.children[0]);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
const parent = context.parent;
|
||
|
if (!parent || parent.type !== 0 /* ROOT */) {
|
||
|
return;
|
||
|
}
|
||
|
if (node.type === 10 /* IF_BRANCH */ && hasSingleChild(node)) {
|
||
|
// detect cases where the parent v-if is not the only root level node
|
||
|
let hasEncounteredIf = false;
|
||
|
for (const c of filterChild(parent)) {
|
||
|
if (c.type === 9 /* IF */ ||
|
||
|
(c.type === 1 /* ELEMENT */ && compilerDom.findDir(c, 'if'))) {
|
||
|
// multiple root v-if
|
||
|
if (hasEncounteredIf)
|
||
|
return;
|
||
|
hasEncounteredIf = true;
|
||
|
}
|
||
|
else if (
|
||
|
// node before v-if
|
||
|
!hasEncounteredIf ||
|
||
|
// non else nodes
|
||
|
!(c.type === 1 /* ELEMENT */ && compilerDom.findDir(c, /else/, true))) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
injectFallthroughAttrs(node.children[0]);
|
||
|
}
|
||
|
else if (hasSingleChild(parent)) {
|
||
|
injectFallthroughAttrs(node);
|
||
|
}
|
||
|
};
|
||
|
function injectFallthroughAttrs(node) {
|
||
|
if (node.type === 1 /* ELEMENT */ &&
|
||
|
(node.tagType === 0 /* ELEMENT */ ||
|
||
|
node.tagType === 1 /* COMPONENT */) &&
|
||
|
!compilerDom.findDir(node, 'for')) {
|
||
|
node.props.push({
|
||
|
type: 7 /* DIRECTIVE */,
|
||
|
name: 'bind',
|
||
|
arg: undefined,
|
||
|
exp: compilerDom.createSimpleExpression(`_attrs`, false),
|
||
|
modifiers: [],
|
||
|
loc: compilerDom.locStub
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ssrInjectCssVars = (node, context) => {
|
||
|
if (!context.ssrCssVars) {
|
||
|
return;
|
||
|
}
|
||
|
// _cssVars is initialized once per render function
|
||
|
// the code is injected in ssrCodegenTransform when creating the
|
||
|
// ssr transform context
|
||
|
if (node.type === 0 /* ROOT */) {
|
||
|
context.identifiers._cssVars = 1;
|
||
|
}
|
||
|
const parent = context.parent;
|
||
|
if (!parent || parent.type !== 0 /* ROOT */) {
|
||
|
return;
|
||
|
}
|
||
|
if (node.type === 10 /* IF_BRANCH */) {
|
||
|
for (const child of node.children) {
|
||
|
injectCssVars(child);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
injectCssVars(node);
|
||
|
}
|
||
|
};
|
||
|
function injectCssVars(node) {
|
||
|
if (node.type === 1 /* ELEMENT */ &&
|
||
|
(node.tagType === 0 /* ELEMENT */ ||
|
||
|
node.tagType === 1 /* COMPONENT */) &&
|
||
|
!compilerDom.findDir(node, 'for')) {
|
||
|
if (compilerDom.isBuiltInType(node.tag, 'Suspense')) {
|
||
|
for (const child of node.children) {
|
||
|
if (child.type === 1 /* ELEMENT */ &&
|
||
|
child.tagType === 3 /* TEMPLATE */) {
|
||
|
// suspense slot
|
||
|
child.children.forEach(injectCssVars);
|
||
|
}
|
||
|
else {
|
||
|
injectCssVars(child);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
node.props.push({
|
||
|
type: 7 /* DIRECTIVE */,
|
||
|
name: 'bind',
|
||
|
arg: undefined,
|
||
|
exp: compilerDom.createSimpleExpression(`_cssVars`, false),
|
||
|
modifiers: [],
|
||
|
loc: compilerDom.locStub
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function compile(template, options = {}) {
|
||
|
options = {
|
||
|
...options,
|
||
|
// apply DOM-specific parsing options
|
||
|
...compilerDom.parserOptions,
|
||
|
ssr: true,
|
||
|
inSSR: true,
|
||
|
scopeId: options.mode === 'function' ? null : options.scopeId,
|
||
|
// always prefix since compiler-ssr doesn't have size concern
|
||
|
prefixIdentifiers: true,
|
||
|
// disable optimizations that are unnecessary for ssr
|
||
|
cacheHandlers: false,
|
||
|
hoistStatic: false
|
||
|
};
|
||
|
const ast = compilerDom.baseParse(template, options);
|
||
|
// Save raw options for AST. This is needed when performing sub-transforms
|
||
|
// on slot vnode branches.
|
||
|
rawOptionsMap.set(ast, options);
|
||
|
compilerDom.transform(ast, {
|
||
|
...options,
|
||
|
hoistStatic: false,
|
||
|
nodeTransforms: [
|
||
|
ssrTransformIf,
|
||
|
ssrTransformFor,
|
||
|
compilerDom.trackVForSlotScopes,
|
||
|
compilerDom.transformExpression,
|
||
|
ssrTransformSlotOutlet,
|
||
|
ssrInjectFallthroughAttrs,
|
||
|
ssrInjectCssVars,
|
||
|
ssrTransformElement,
|
||
|
ssrTransformComponent,
|
||
|
compilerDom.trackSlotScopes,
|
||
|
compilerDom.transformStyle,
|
||
|
...(options.nodeTransforms || []) // user transforms
|
||
|
],
|
||
|
directiveTransforms: {
|
||
|
// reusing core v-bind
|
||
|
bind: compilerDom.transformBind,
|
||
|
on: compilerDom.transformOn,
|
||
|
// model and show has dedicated SSR handling
|
||
|
model: ssrTransformModel,
|
||
|
show: ssrTransformShow,
|
||
|
// the following are ignored during SSR
|
||
|
// on: noopDirectiveTransform,
|
||
|
cloak: compilerDom.noopDirectiveTransform,
|
||
|
once: compilerDom.noopDirectiveTransform,
|
||
|
memo: compilerDom.noopDirectiveTransform,
|
||
|
...(options.directiveTransforms || {}) // user transforms
|
||
|
}
|
||
|
});
|
||
|
// traverse the template AST and convert into SSR codegen AST
|
||
|
// by replacing ast.codegenNode.
|
||
|
ssrCodegenTransform(ast, options);
|
||
|
return compilerDom.generate(ast, options);
|
||
|
}
|
||
|
|
||
|
exports.compile = compile;
|