UNPKG

@hippy/vue-next-compiler-ssr

Version:

Vue-Next server render compiler for Hippy native framework

1,148 lines (1,134 loc) 75.3 kB
/*! * @hippy/vue-next-compiler-ssr v3.3.2 * (Using Vue v3.4.32 and Hippy-Vue-Next v3.3.2) * Build at: Fri Nov 08 2024 20:45:13 GMT+0800 (中国标准时间) * * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2023-2024 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var compilerDom = require('@vue/compiler-dom'); var shared = require('@vue/shared'); /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function createSSRCompilerError(code, loc) { return compilerDom.createCompilerError(code, loc, SSRErrorMessages); } const SSRErrorMessages = { [64 /* SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME */]: 'Unsafe attribute name for SSR.', [65 /* SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET */]: 'Missing the \'to\' prop on teleport element.', [66 /* SSRErrorCodes.X_SSR_INVALID_AST_NODE */]: 'Invalid AST node during SSR transform.', }; /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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'); // provided by @hippy/vue-next-server-renderer const SSR_GET_UNIQUEID = Symbol('ssrGetUniqueId'); 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', [SSR_GET_UNIQUEID]: 'ssrGetUniqueId', }; // Note: these are helpers imported from @vue/server-renderer // make sure the names match! compilerDom.registerRuntimeHelpers(ssrHelpers); /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable complexity */ /** * get hippy native view's name, map with html tag * * @param tag - html tag or Vue component name */ function getHippyNativeViewName(tag) { // Hippy built-in native view const NATIVE_COMPONENT_MAP = { View: 'View', Image: 'Image', ListView: 'ListView', ListViewItem: 'ListViewItem', Text: 'Text', TextInput: 'TextInput', WebView: 'WebView', VideoPlayer: 'VideoPlayer', // native inner view, just name different with View ScrollView: 'ScrollView', Swiper: 'ViewPager', SwiperSlide: 'ViewPagerItem', PullHeaderView: 'PullHeaderView', PullFooterView: 'PullFooterView', Dialog: 'Modal', UlRefreshWrapper: 'RefreshWrapper', UlRefresh: 'RefreshWrapperItemView', Waterfall: 'WaterfallView', WaterfallItem: 'WaterfallItem', }; switch (tag) { case 'div': case 'button': case 'form': return NATIVE_COMPONENT_MAP.View; case 'img': return NATIVE_COMPONENT_MAP.Image; case 'ul': return NATIVE_COMPONENT_MAP.ListView; case 'li': return NATIVE_COMPONENT_MAP.ListViewItem; case 'span': case 'label': case 'p': case 'a': return NATIVE_COMPONENT_MAP.Text; case 'textarea': case 'input': return NATIVE_COMPONENT_MAP.TextInput; case 'iframe': return NATIVE_COMPONENT_MAP.WebView; case 'hi-swiper': return NATIVE_COMPONENT_MAP.Swiper; case 'hi-swiper-slide': return NATIVE_COMPONENT_MAP.SwiperSlide; case 'hi-pull-header': return NATIVE_COMPONENT_MAP.PullHeaderView; case 'hi-pull-footer': return NATIVE_COMPONENT_MAP.PullFooterView; case 'dialog': return NATIVE_COMPONENT_MAP.Dialog; case 'hi-ul-refresh-wrapper': return NATIVE_COMPONENT_MAP.UlRefreshWrapper; case 'hi-refresh-wrapper-item': return NATIVE_COMPONENT_MAP.UlRefresh; case 'hi-waterfall': return NATIVE_COMPONENT_MAP.Waterfall; case 'hi-waterfall-item': return NATIVE_COMPONENT_MAP.WaterfallItem; default: return tag; } } /** * parse vue tag/component name to real tag name * * @param compName */ function getHippyTagName(compName) { // do not have need parse tag name now return compName; } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // 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(); /** * return tag is text or not * * @param tag - tag name */ function isTextTag(tag) { return ['span', 'p', 'label', 'a'].includes(tag); } const ssrTransformElement = (node, context) => { if (node.type !== compilerDom.NodeTypes.ELEMENT || node.tagType !== compilerDom.ElementTypes.ELEMENT) { return; } return function ssrPostTransformElement() { var _a; const openTag = ['{"id":']; // generate hippy uniqueId openTag.push(compilerDom.createCallExpression(context.helper(SSR_GET_UNIQUEID), [])); // push name and tagName attr openTag.push(`,"index":0,"name":"${getHippyNativeViewName(node.tag)}","tagName":"${getHippyTagName(node.tag)}","props":{`); // 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 === compilerDom.NodeTypes.DIRECTIVE && !shared.isBuiltInDirective(p.name)); const needMergeProps = hasDynamicVBind || hasCustomDir; // hippy images source, Android use src=imgUrl, iOS use source=[{"uri": imgUrl}] // numberOfRows will makes Image flicker on Android, iOS only // all props of hippy node(except id and class) will store in "props" and "attributes"(for debug) // need merge props should merge in node props when at client runtime, not in ssr 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); openTag.push('"mergedProps":'); openTag.push(compilerDom.createCallExpression('JSON.stringify', [mergedProps])); openTag.push(','); } } // bookkeeping 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 === compilerDom.NodeTypes.DIRECTIVE) { if (prop.name === 'on') ; else if (prop.name === 'html' && prop.exp) ; else if (prop.name === 'text' && prop.exp) ; else if (prop.name === 'slot') { context.onError(compilerDom.createCompilerError(compilerDom.ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)); } else if (isTextareaWithValue(node, prop) && prop.exp) ; else if (!needMergeProps && prop.name !== 'on') { // Directive transforms. const directiveTransform = context.directiveTransforms[prop.name]; if (directiveTransform) { const { props, ssrTagParts } = directiveTransform(prop, node, context); 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 : (_a = shared.propsToAttrMap[attrName]) !== null && _a !== void 0 ? _a : 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(`"${key.content}":`); openTag.push(compilerDom.createCallExpression('JSON.stringify', [value])); openTag.push(','); } else { context.onError(createSSRCompilerError(64 /* SSRErrorCodes.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> // eslint-disable-next-line no-lonely-if if (node.tag === 'textarea' && prop.name === 'value' && prop.value) { rawChildrenMap.set(node, 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 ? `"${prop.value.content}",` : '"",'}`); } } } // handle co-existence of dynamic + static class bindings if (dynamicClassBinding && staticClassBinding) { // convert hippy style, static class insert to style props. dynamic class generate getStyle function mergeCall(dynamicClassBinding, staticClassBinding); removeStaticBinding(openTag, 'class'); } // span/label/p/a, these tags are text node in native, so the value store in text props if (isTextTag(node.tag)) { const textChild = node.children.filter(item => item.type === compilerDom.NodeTypes.TEXT || item.type === compilerDom.NodeTypes.INTERPOLATION); if (textChild.length) { // 2(NodeTypes.TEXT),5(NodeTypes.INTERPOLATION) 2 and 5 are text node, store value in text props openTag.push('"text":"'); textChild.forEach((child) => { if (child.type === 2 /* NodeTypes.TEXT */) { openTag.push(child.content); } if (child.type === 5 /* NodeTypes.INTERPOLATION */) { openTag.push(compilerDom.createCallExpression(context.helper(SSR_INTERPOLATE), [ child.content, ])); } }); openTag.push('",'); } } if (context.scopeId) { // hippy scopeId just set props, like <div data-v-sdkfj12="true" /> openTag.push(`"${context.scopeId}":""`); openTag.push(','); } openTag.push('},'); node.ssrCodegenNode = compilerDom.createTemplateLiteral(openTag); }; }; function buildSSRProps(props, directives, context) { let mergePropsArgs = []; if (props) { if (props.type === compilerDom.NodeTypes.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 === compilerDom.NodeTypes.DIRECTIVE) { return (prop.name === 'bind' && prop.arg && compilerDom.isStaticExp(prop.arg) && (prop.arg.content === 'true-value' || prop.arg.content === 'false-value')); } return prop.name === 'true-value' || prop.name === 'false-value'; } function isTextareaWithValue(node, prop) { return Boolean(node.tag === 'textarea' && prop.name === 'bind' && compilerDom.isStaticArgOf(prop.arg, 'value')); } function mergeCall(call, arg) { const existing = call.arguments[0]; if (existing.type === compilerDom.NodeTypes.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 ssrProcessElement(node, context) { 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)); } // hippy children open tag context.pushStringPart('"children":['); const rawChildren = rawChildrenMap.get(node); if (rawChildren) { context.pushStringPart(rawChildren); } else if (node.children.length) { processChildren(node, context); } // closing tag context.pushStringPart(']},'); } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const wipMap$2 = new WeakMap(); // phase 1 function ssrTransformSuspense(node, context) { return () => { if (node.children.length) { const wipEntry = { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion slotsExp: null, wipSlots: [], }; wipMap$2.set(node, wipEntry); wipEntry.slotsExp = compilerDom.buildSlots(node, context, (_props, _vForExp, 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$2.get(node); if (!wipEntry) { return; } const { slotsExp, wipSlots } = wipEntry; // eslint-disable-next-line @typescript-eslint/prefer-for-of 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, ])); } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Note: this is a 2nd-pass codegen transform. function ssrProcessTeleport(node, context) { var _a; const targetProp = compilerDom.findProp(node, 'to'); if (!targetProp) { context.onError(createSSRCompilerError(65 /* SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET */, node.loc)); return; } let target; if (targetProp.type === compilerDom.NodeTypes.ATTRIBUTE) { target = targetProp.value && compilerDom.createSimpleExpression(targetProp.value.content, true); } else { target = targetProp.exp; } if (!target) { context.onError(createSSRCompilerError(65 /* SSRErrorCodes.X_SSR_NO_TELEPORT_TARGET */, targetProp.loc)); return; } const disabledProp = compilerDom.findProp(node, 'disabled', false, true /* allow empty */); // eslint-disable-next-line no-nested-ternary const disabled = disabledProp ? disabledProp.type === compilerDom.NodeTypes.ATTRIBUTE ? 'true' : (_a = disabledProp.exp) !== null && _a !== void 0 ? _a : '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', ])); } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; 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 === compilerDom.NodeTypes.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); } } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // 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 = new WeakMap(); // eslint-disable-next-line symbol-description 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 !== compilerDom.NodeTypes.ELEMENT || node.tagType !== compilerDom.ElementTypes.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, vFor, children) => { vnodeBranches.push(createVNodeSlotBranch(props, vFor, 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.set(node, wipEntries); const buildSSRSlotFn = (props, _vForExp, children, loc) => { const fn = compilerDom.createFunctionExpression([props !== null && props !== void 0 ? 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']); } }; }; // eslint-disable-next-line complexity function ssrProcessComponent(node, context, parent) { var _a; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 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 !== compilerDom.NodeTypes.COMMENT); } processChildren(node, context); } } else { // finish up slot function expressions from the 1st pass. const wipEntries = (_a = wipMap.get(node)) !== null && _a !== void 0 ? _a : []; 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(slotProps, vFor, children, parentContext) { var _a, _b; // apply a sub-transform using vnode-based transforms. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const rawOptions = rawOptionsMap.get(parentContext.root); const subOptions = { ...rawOptions, // overwrite with vnode-based transforms nodeTransforms: [ ...vnodeNodeTransforms, ...((_a = rawOptions.nodeTransforms) !== null && _a !== void 0 ? _a : []), ], directiveTransforms: { ...vnodeDirectiveTransforms, ...((_b = rawOptions.directiveTransforms) !== null && _b !== void 0 ? _b : {}), }, }; // wrap the children with a wrapper template for proper children treatment. // important: provide v-slot="props" and v-for="exp" on the wrapper for // proper scope analysis const wrapperProps = []; if (slotProps) { wrapperProps.push({ type: compilerDom.NodeTypes.DIRECTIVE, name: 'slot', exp: slotProps, arg: undefined, modifiers: [], loc: compilerDom.locStub, }); } if (vFor) { wrapperProps.push(shared.extend({}, vFor)); } const wrapperNode = { type: compilerDom.NodeTypes.ELEMENT, ns: compilerDom.Namespaces.HTML, tag: 'template', tagType: compilerDom.ElementTypes.TEMPLATE, isSelfClosing: false, props: wrapperProps, 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); // merge helpers/components/directives into parent context ['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 = {}; // eslint-disable-next-line guard-for-in, no-restricted-syntax for (const key in v) { res[key] = clone(v[key]); } return res; } else { return v; } } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // eslint-disable-next-line complexity const ssrTransformSlotOutlet = (node, context) => { if (compilerDom.isSlotOutlet(node)) { const { slotName, slotProps } = compilerDom.processSlotOutlet(node, context); const args = [ '_ctx.$slots', slotName, slotProps !== null && slotProps !== void 0 ? slotProps : '{}', // fallback content placeholder. will be replaced in the process phase 'null', '_push', '_parent', ]; // inject slot scope id if current template uses :slotted // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare 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; if (parent && parent.type === compilerDom.NodeTypes.ELEMENT && parent.tagType === compilerDom.ElementTypes.COMPONENT && compilerDom.resolveComponentType(parent, context, true) === compilerDom.TRANSITION && parent.children.filter(c => c.type === compilerDom.NodeTypes.ELEMENT).length === 1) { method = SSR_RENDER_SLOT_INNER; // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (!(context.scopeId && context.slotted !== false)) { args.push('null'); } args.push('true'); } // eslint-disable-next-line no-param-reassign 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); } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // 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 !== compilerDom.NodeTypes.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('{"id": -1,"name":"comment","props":{"text":"["}},'); } context.pushStatement(compilerDom.createCallExpression(context.helper(SSR_RENDER_LIST), [ node.source, renderLoop, ])); if (!disableNestedFragments) { context.pushStringPart('{"id": -1,"name":"comment","props":{"text":"]"}},'); } } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2022 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // 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( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 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 // eslint-disable-next-line no-multi-assign currentIf = currentIf.alternate = compilerDom.createIfStatement(branch.condition, branchBlockStatement); } else { // else currentIf.alternate = branchBlockStatement; } } if (!currentIf.alternate) { currentIf.alternate = compilerDom.createBlockStatement([ compilerDom.createCallExpression('_push', [ '`{"id": -1,"name":"comment","props":{"text":""}},`', ]), ]); } } functi