'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_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 ssrHelpers = { [SSR_INTERPOLATE]: `ssrInterpolate`, [SSR_RENDER_VNODE]: `ssrRenderVNode`, [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_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` }; // 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(children, 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.children, 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"`); } node.ssrCodegenNode = compilerDom.createCallExpression(context.helper(SSR_RENDER_SLOT), args); } }; function ssrProcessSlotOutlet(node, context) { const renderCall = node.ssrCodegenNode; // has fallback content if (node.children.length) { const fallbackRenderFn = compilerDom.createFunctionExpression([]); fallbackRenderFn.body = processChildrenAsStatement(node.children, 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_CUSTOM_DIRECTIVE_NO_TRANSFORM */]: `Custom directive is missing corresponding SSR transform and will be ignored.`, [62 /* X_SSR_UNSAFE_ATTR_NAME */]: `Unsafe attribute name for SSR.`, [63 /* X_SSR_NO_TELEPORT_TARGET */]: `Missing the 'to' prop on teleport element.`, [64 /* 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(63 /* 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(63 /* 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.children, 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 { fn, children } = wipSlots[i]; fn.body = processChildrenAsStatement(children, context); } // _push(ssrRenderSuspense(slots)) context.pushStatement(compilerDom.createCallExpression(context.helper(SSR_RENDER_SUSPENSE), [ `_push`, slotsExp ])); } function ssrProcessTransitionGroup(node, context) { const tag = compilerDom.findProp(node, 'tag'); if (tag) { if (tag.type === 7 /* DIRECTIVE */) { // dynamic :tag context.pushStringPart(`<`); context.pushStringPart(tag.exp); context.pushStringPart(`>`); processChildren(node.children, 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}>`); processChildren(node.children, context, false, true); context.pushStringPart(`</${tag.value.content}>`); } } else { // fragment processChildren(node.children, 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$1 = new WeakMap(); 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 */); componentTypeMap.set(node, component); if (shared.isSymbol(component)) { if (component === compilerDom.SUSPENSE) { return ssrTransformSuspense(node, context); } return; // built-in component: 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); }); } const props = node.props.length > 0 ? // note we are not passing ssr: true here because for components, v-on // handlers should still be passed compilerDom.buildProps(node, context).props || `null` : `null`; const wipEntries = []; wipMap$1.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({ 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, props, slots ]), `_parent` ]); } else { node.ssrCodegenNode = compilerDom.createCallExpression(context.helper(SSR_RENDER_COMPONENT), [component, props, slots, `_parent`]); } }; }; function ssrProcessComponent(node, context) { 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. processChildren(node.children, context); } } else { // finish up slot function expressions from the 1st pass. const wipEntries = wipMap$1.get(node) || []; for (let i = 0; i < wipEntries.length; i++) { const { fn, children, 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(children, 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 = Object.assign(Object.assign({}, baseDirectiveTransforms), compilerDom.DOMDirectiveTransforms); function createVNodeSlotBranch(props, children, parentContext) { // apply a sub-transform using vnode-based transforms. const rawOptions = rawOptionsMap.get(parentContext.root); const subOptions = Object.assign(Object.assign({}, rawOptions), { // overwrite with vnode-based transforms nodeTransforms: [ ...vnodeNodeTransforms, ...(rawOptions.nodeTransforms || []) ], directiveTransforms: Object.assign(Object.assign({}, 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 = Object.assign({}, parentContext.scopes); childContext.identifiers = Object.assign({}, 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; } } // 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" or v-bind:[key] 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); if (hasDynamicVBind) { const { props } = compilerDom.buildProps(node, context, node.props, true /* ssr */); if (props) { const propsExp = compilerDom.createCallExpression(context.helper(SSR_RENDER_ATTRS), [props]); 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), props) ]; 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, props), 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 (!hasDynamicVBind) { node.children = [compilerDom.createInterpolation(prop.exp, prop.loc)]; } } else { // Directive transforms. const directiveTransform = context.directiveTransforms[prop.name]; if (!directiveTransform) { // no corresponding ssr directive transform found. context.onError(createSSRCompilerError(61 /* X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM */, prop.loc)); } else if (!hasDynamicVBind) { 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(62 /* 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 (!hasDynamicVBind) { 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 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.children, context); } if (!isVoidTag(node.tag)) { // push closing tag context.pushStringPart(`</${node.tag}>`); } } // 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.children, 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(children, context, asFragment = false, disableNestedFragments = false) { if (asFragment) { context.pushStringPart(`<!--[-->`); } 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); break; case 2 /* SLOT */: ssrProcessSlotOutlet(child, context); break; case 3 /* TEMPLATE */: // TODO break; default: context.onError(createSSRCompilerError(64 /* 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(64 /* 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(children, parentContext, asFragment = false, withSlotScopeId = parentContext.withSlotScopeId) { const childContext = createChildContext(parentContext, withSlotScopeId); processChildren(children, 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 hasSingleChild = (node) => node.children.filter(n => n.type !== 3 /* COMMENT */).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'))) { 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)) { 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 = Object.assign(Object.assign(Object.assign({}, 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, Object.assign(Object.assign({}, options), { hoistStatic: false, nodeTransforms: [ ssrTransformIf, ssrTransformFor, compilerDom.trackVForSlotScopes, compilerDom.transformExpression, ssrTransformSlotOutlet, ssrInjectFallthroughAttrs, ssrInjectCssVars, ssrTransformElement, ssrTransformComponent, compilerDom.trackSlotScopes, compilerDom.transformStyle, ...(options.nodeTransforms || []) // user transforms ], directiveTransforms: Object.assign({ // reusing core v-bind bind: compilerDom.transformBind, // model and show has dedicated SSR handling model: ssrTransformModel, show: ssrTransformShow, // the following are ignored during SSR on: compilerDom.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;