@hippy/vue-next-compiler-ssr
Version:
Vue-Next server render compiler for Hippy native framework
1,148 lines (1,134 loc) • 75.3 kB
JavaScript
/*!
* @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