fix(build): SSR 安全化 PlaylistCardGenerator canvasHeight 避免 Nitro 构建 OOM(exit 137)
上版在 canvasHeight computed 里对每首歌 document.createElement('canvas')+getContext('2d'),Nitro prerender/rollup bundler 在 Node 环境下要么 document undefined 抛错 fallback,要么 prerender 时对大歌单反复创建 2D context 撑爆虚拟内存 -> 5min 后 OOM Killer(exit 137)。
修复:
- 新增 isBrowser = typeof document !== 'undefined' 判断
- getSharedCtx(): 浏览器环境才创建 1 个共享 2D context 并跨次复用(不再每首歌 createElement)
- fitText(ctx, ...) 抽成双分支:有 ctx 用 measureText(客户端精确),无 ctx 用 CJK>0x2e80 Unicode 判断+经验字符宽度(16px 中文 1.05em/英文 0.55em)估算换行,保守估算高度
- 同时 fitText 支持文本中带 \\n 换行手动切分
- canvasHeight 中不再 new canvas/context,SSR 环境也能安全执行+算出估算高度
This commit is contained in:
@@ -91,30 +91,69 @@ const songLeftX = 44
|
|||||||
const songRightX = 360
|
const songRightX = 360
|
||||||
const songAreaWidth = songRightX - songLeftX
|
const songAreaWidth = songRightX - songLeftX
|
||||||
|
|
||||||
|
const isBrowser = typeof document !== 'undefined'
|
||||||
|
|
||||||
|
let _sharedCtx = null
|
||||||
|
const getSharedCtx = () => {
|
||||||
|
if (!isBrowser) return null
|
||||||
|
if (_sharedCtx) return _sharedCtx
|
||||||
|
try {
|
||||||
|
const c = document.createElement('canvas')
|
||||||
|
_sharedCtx = c.getContext('2d')
|
||||||
|
} catch { _sharedCtx = null }
|
||||||
|
return _sharedCtx
|
||||||
|
}
|
||||||
|
|
||||||
const fitText = (ctx, text, fontSize, maxWidth, lineHeight) => {
|
const fitText = (ctx, text, fontSize, maxWidth, lineHeight) => {
|
||||||
ctx.font = `${fontSize}px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif`
|
const hasCtx = !!ctx
|
||||||
const chars = Array.from(text)
|
const fontFamily = `"Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif`
|
||||||
|
const avgCharWidth = fontSize * 0.55
|
||||||
|
const cjkCharWidth = fontSize * 1.05
|
||||||
|
const splitLines = String(text || '').split(/\r?\n/)
|
||||||
const lines = []
|
const lines = []
|
||||||
let current = ''
|
for (const rawLine of splitLines) {
|
||||||
for (const ch of chars) {
|
if (hasCtx) {
|
||||||
const tryStr = current + ch
|
ctx.font = `${fontSize}px ${fontFamily}`
|
||||||
const w = ctx.measureText(tryStr).width
|
const chars = Array.from(rawLine)
|
||||||
if (w > maxWidth && current) {
|
let current = ''
|
||||||
lines.push(current)
|
for (const ch of chars) {
|
||||||
current = ch
|
const tryStr = current + ch
|
||||||
|
const w = ctx.measureText(tryStr).width
|
||||||
|
if (w > maxWidth && current) {
|
||||||
|
lines.push(current)
|
||||||
|
current = ch
|
||||||
|
} else {
|
||||||
|
current = tryStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current) lines.push(current)
|
||||||
} else {
|
} else {
|
||||||
current = tryStr
|
const chars = Array.from(rawLine)
|
||||||
|
let current = ''
|
||||||
|
let curW = 0
|
||||||
|
for (const ch of chars) {
|
||||||
|
const code = ch.codePointAt(0) || 0
|
||||||
|
const isCJK = code > 0x2e80
|
||||||
|
const w = isCJK ? cjkCharWidth : avgCharWidth
|
||||||
|
if (curW + w > maxWidth && current) {
|
||||||
|
lines.push(current)
|
||||||
|
current = ch
|
||||||
|
curW = w
|
||||||
|
} else {
|
||||||
|
current += ch
|
||||||
|
curW += w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current) lines.push(current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current) lines.push(current)
|
|
||||||
return { lines, lineHeight }
|
return { lines, lineHeight }
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasHeight = computed(() => {
|
const canvasHeight = computed(() => {
|
||||||
const count = Math.min(props.songs.length, maxDisplaySongs)
|
const count = Math.min(props.songs.length, maxDisplaySongs)
|
||||||
if (!count) return 600
|
if (!count) return 600
|
||||||
const canvas = document.createElement('canvas')
|
const ctx = getSharedCtx()
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
let totalH = 0
|
let totalH = 0
|
||||||
const maxWidthTitle = songAreaWidth
|
const maxWidthTitle = songAreaWidth
|
||||||
const maxWidthArtist = Math.max(100, songAreaWidth - 10)
|
const maxWidthArtist = Math.max(100, songAreaWidth - 10)
|
||||||
|
|||||||
Reference in New Issue
Block a user