ssrRenderAttrs.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {
  2. escapeHtml,
  3. isArray,
  4. isObject,
  5. isRenderableAttrValue,
  6. isSVGTag,
  7. stringifyStyle,
  8. } from '@vue/shared'
  9. import {
  10. includeBooleanAttr,
  11. isBooleanAttr,
  12. isOn,
  13. isSSRSafeAttrName,
  14. isString,
  15. makeMap,
  16. normalizeClass,
  17. normalizeCssVarValue,
  18. normalizeStyle,
  19. propsToAttrMap,
  20. } from '@vue/shared'
  21. // leading comma for empty string ""
  22. const shouldIgnoreProp = /*@__PURE__*/ makeMap(
  23. `,key,ref,innerHTML,textContent,ref_key,ref_for`,
  24. )
  25. export function ssrRenderAttrs(
  26. props: Record<string, unknown>,
  27. tag?: string,
  28. ): string {
  29. let ret = ''
  30. for (const key in props) {
  31. if (
  32. shouldIgnoreProp(key) ||
  33. isOn(key) ||
  34. (tag === 'textarea' && key === 'value')
  35. ) {
  36. continue
  37. }
  38. const value = props[key]
  39. if (key === 'class') {
  40. ret += ` class="${ssrRenderClass(value)}"`
  41. } else if (key === 'style') {
  42. ret += ` style="${ssrRenderStyle(value)}"`
  43. } else if (key === 'className') {
  44. ret += ` class="${String(value)}"`
  45. } else {
  46. ret += ssrRenderDynamicAttr(key, value, tag)
  47. }
  48. }
  49. return ret
  50. }
  51. // render an attr with dynamic (unknown) key.
  52. export function ssrRenderDynamicAttr(
  53. key: string,
  54. value: unknown,
  55. tag?: string,
  56. ): string {
  57. if (!isRenderableAttrValue(value)) {
  58. return ``
  59. }
  60. const attrKey =
  61. tag && (tag.indexOf('-') > 0 || isSVGTag(tag))
  62. ? key // preserve raw name on custom elements and svg
  63. : propsToAttrMap[key] || key.toLowerCase()
  64. if (isBooleanAttr(attrKey)) {
  65. return includeBooleanAttr(value) ? ` ${attrKey}` : ``
  66. } else if (isSSRSafeAttrName(attrKey)) {
  67. return value === '' ? ` ${attrKey}` : ` ${attrKey}="${escapeHtml(value)}"`
  68. } else {
  69. console.warn(
  70. `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`,
  71. )
  72. return ``
  73. }
  74. }
  75. // Render a v-bind attr with static key. The key is pre-processed at compile
  76. // time and we only need to check and escape value.
  77. export function ssrRenderAttr(key: string, value: unknown): string {
  78. if (!isRenderableAttrValue(value)) {
  79. return ``
  80. }
  81. return ` ${key}="${escapeHtml(value)}"`
  82. }
  83. export function ssrRenderClass(raw: unknown): string {
  84. return escapeHtml(normalizeClass(raw))
  85. }
  86. export function ssrRenderStyle(raw: unknown): string {
  87. if (!raw) {
  88. return ''
  89. }
  90. if (isString(raw)) {
  91. return escapeHtml(raw)
  92. }
  93. const styles = normalizeStyle(ssrResetCssVars(raw))
  94. return escapeHtml(stringifyStyle(styles))
  95. }
  96. function ssrResetCssVars(raw: unknown) {
  97. if (!isArray(raw) && isObject(raw)) {
  98. const res: Record<string, unknown> = {}
  99. for (const key in raw) {
  100. // `:` prefixed keys are coming from `ssrCssVars`
  101. if (key.startsWith(':--')) {
  102. res[key.slice(1)] = normalizeCssVarValue(raw[key])
  103. } else {
  104. res[key] = raw[key]
  105. }
  106. }
  107. return res
  108. }
  109. return raw
  110. }