@withl5e/l5e 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/defineMiddleware-VdHQuiUc.js +7 -0
- package/dist/defineMiddleware-VdHQuiUc.js.map +1 -0
- package/dist/entry-server.js +1 -1
- package/dist/{generateMetadata-CIFVlViV.js → generateMetadata-Cn2uJjg_.js} +6 -6
- package/dist/generateMetadata-Cn2uJjg_.js.map +1 -0
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -0
- package/dist/{index-BlfNBupp.js → index-CybB8Tdk.js} +30 -33
- package/dist/index-CybB8Tdk.js.map +1 -0
- package/dist/index.js +24 -23
- package/dist/index.js.map +1 -1
- package/dist/middleware.js +5 -4
- package/dist/middleware.js.map +1 -1
- package/dist/seo.js +1 -1
- package/dist/server.js +301 -300
- package/dist/server.js.map +1 -1
- package/dist/tooltip.js +92 -74
- package/dist/tooltip.js.map +1 -1
- package/package.json +5 -1
- package/src/core/jsx-types.d.ts +1 -0
- package/src/core/server.ts +50 -27
- package/src/i18n/index.ts +2 -0
- package/src/i18n/middleware.ts +50 -0
- package/src/i18n/types.ts +13 -0
- package/src/seo/generateMetadata.tsx +7 -2
- package/src/seo/types.ts +16 -0
- package/src/tooltip/index.ts +2 -1
- package/src/tooltip/tooltip-runtime.ts +71 -4
- package/dist/generateMetadata-CIFVlViV.js.map +0 -1
- package/dist/index-BlfNBupp.js.map +0 -1
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/core/bundler.ts","../src/core/server.ts"],"sourcesContent":["/// <reference path=\"./jsx-types.d.ts\" />\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { OutputOptions, RollupOptions } from 'rollup';\n\nlet rollupModulePromise: Promise<typeof import('rollup')> | null = null;\n\n/**\n * Resolve rollup lazily. We can't `import 'rollup'` directly because when the\n * framework gets bundled into the consumer's SSR output the bare specifier is\n * hoisted to a static import that pnpm doesn't satisfy. Resolve through `vite`\n * instead — vite is a peer dep so it's always installed, and it always ships\n * rollup as a direct dependency.\n */\nfunction loadRollup(): Promise<typeof import('rollup')> {\n if (!rollupModulePromise) {\n rollupModulePromise = (async () => {\n const require = createRequire(import.meta.url);\n const vitePath = require.resolve('vite');\n const rollupPath = require.resolve('rollup', { paths: [path.dirname(vitePath)] });\n return (await import(pathToFileURL(rollupPath).href)) as typeof import('rollup');\n })();\n }\n return rollupModulePromise;\n}\n\ninterface BundledFile {\n content: string;\n hash: string;\n filename: string;\n mimeType: string;\n}\n\n// Memory map để lưu bundled files\nconst bundledFilesMap = new Map<string, BundledFile>();\n\n// Cache map để deduplicate bundling requests (cacheKey → entry chunk fileName)\nconst bundleCache = new Map<string, string>();\nconst cssCache = new Map<string, string>();\n\n/**\n * Generate hash từ content\n */\nfunction generateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex').substring(0, 16);\n}\n\n/**\n * Bundle JavaScript files từ dist/client thành 1 file\n * Trong production, các file đã được build sẵn trong dist/client\n */\nexport async function bundleScripts(\n scriptPaths: string[],\n rootDir: string,\n distClientDir: string,\n): Promise<{ hash: string; filename: string; content: string }> {\n if (scriptPaths.length === 0) {\n return { hash: '', filename: '', content: '' };\n }\n\n // Dedupe paths (remove duplicates)\n const uniquePaths = [...new Set(scriptPaths)];\n\n // Tạo cache key từ sorted unique paths\n const cacheKey = `scripts:${uniquePaths.sort().join(',')}`;\n\n // Kiểm tra cache - return entry chunk info if already bundled\n const cachedEntryFileName = bundleCache.get(cacheKey);\n if (cachedEntryFileName) {\n const entryFile = bundledFilesMap.get(cachedEntryFileName);\n if (entryFile) {\n return {\n hash: entryFile.hash,\n filename: entryFile.filename,\n content: entryFile.content,\n };\n }\n }\n\n // Temp file path for cleanup\n let entryFile: string | null = null;\n\n try {\n // Sử dụng rollup để bundle nếu cần (resolve imports, etc)\n // Tạo temp entry file\n const hash = generateHash(uniquePaths.join('\\n'));\n const tempDir = path.join(rootDir, '.temp-bundle');\n await fs.mkdir(tempDir, { recursive: true }).catch(() => {});\n\n entryFile = path.join(tempDir, `entry-${hash}.js`);\n // Tạo entry file import tất cả scripts\n const entryContent = uniquePaths\n .map((p, i) => {\n const filePath = p.startsWith('/')\n ? path.join(distClientDir, p.substring(1))\n : path.join(distClientDir, p);\n return `import ${JSON.stringify(filePath)};`;\n })\n .join('\\n');\n\n await fs.writeFile(entryFile, entryContent, 'utf-8');\n console.log(`[bundler] Wrote entry file to ${entryFile}`);\n console.log(`[bundler] Entry content: ${entryContent}`);\n // Rollup config để bundle\n const rollupOptions: RollupOptions = {\n input: entryFile,\n plugins: [\n {\n name: 'vendor-path-rewriter',\n resolveId(source, importer, _options) {\n // Handle vendor/chunk/global files: convert absolute paths to web paths\n // Global files (*.global.*) are already loaded by client.global.ts —\n // re-bundling them would create duplicate module instances (e.g. nanostores)\n if (\n source.includes('vendor-') ||\n source.includes('chunk-') ||\n source.includes('.global')\n ) {\n console.log(`[bundler] Resolving source: ${source}`);\n if (path.isAbsolute(source)) {\n console.log(`[bundler] Resolving absolute path: ${source}`);\n // e.g., C:\\...\\dist\\client\\assets\\vendor-react-XXX.js -> /assets/vendor-react-XXX.js\n const relativePath = path.relative(distClientDir, source);\n const webPath = '/' + relativePath.replace(/\\\\/g, '/');\n return { id: webPath, external: true };\n } else if (importer && source.startsWith('.')) {\n console.log(\n `[bundler] Resolving relative path: ${source} from importer: ${importer}`,\n );\n // Relative path like ./auth.global-BOVr81Z5.js — resolve from importer\n const resolved = path.resolve(path.dirname(importer), source);\n const relativePath = path.relative(distClientDir, resolved);\n const webPath = '/' + relativePath.replace(/\\\\/g, '/');\n return { id: webPath, external: true };\n } else {\n console.log(`[bundler] Resolving source: ${source}`);\n }\n }\n return null; // Let other plugins/external handle\n },\n },\n ],\n external: (id) => {\n // External node_modules\n if (!id.startsWith('.') && !path.isAbsolute(id)) {\n return true;\n }\n\n // Let plugin handle vendor/chunk/global files (don't mark external here)\n if (id.includes('vendor-') || id.includes('chunk-') || id.includes('.global')) {\n return false; // Let plugin's resolveId handle path rewriting\n }\n\n return false;\n },\n };\n\n const outputOptions: OutputOptions = {\n format: 'es',\n inlineDynamicImports: false,\n entryFileNames: 'bundle-[hash].js',\n chunkFileNames: 'bundle-[hash].js',\n };\n\n const { rollup } = await loadRollup();\n const bundle = await rollup(rollupOptions);\n const { output } = await bundle.generate(outputOptions);\n await bundle.close();\n\n // Lấy bundled content từ rollup\n\n output.forEach((o) => {\n if (o.type !== 'chunk') {\n return;\n }\n // Lưu vào map với key = fileName\n const bundledFile: BundledFile = {\n content: o.code || '',\n hash: generateHash(o.code || ''),\n filename: o.fileName,\n mimeType: 'application/javascript',\n };\n bundledFilesMap.set(o.fileName, bundledFile);\n });\n\n // Cache entry chunk fileName for deduplication\n const entryChunk = output[0];\n if (entryChunk?.type === 'chunk') {\n bundleCache.set(cacheKey, entryChunk.fileName);\n }\n\n // Return entry chunk info\n return {\n hash: generateHash(output[0]?.code || ''),\n filename: output[0]?.fileName || '',\n content: output[0]?.code || '',\n };\n } catch (error) {\n console.error('[bundler] Error bundling scripts:', error);\n return { hash: '', filename: '', content: '' };\n } finally {\n // Cleanup temp entry file\n if (entryFile) {\n await fs.unlink(entryFile).catch(() => {\n // Ignore cleanup errors\n });\n }\n }\n}\n\n/**\n * Bundle CSS files từ dist/client thành 1 file\n * Trong production, các file đã được build sẵn trong dist/client\n */\nexport async function bundleCss(\n cssPaths: string[],\n rootDir: string,\n distClientDir: string,\n): Promise<{ hash: string; filename: string; content: string }> {\n if (cssPaths.length === 0) {\n return { hash: '', filename: '', content: '' };\n }\n\n // Dedupe paths (remove duplicates)\n const uniquePaths = [...new Set(cssPaths)];\n\n // Tạo cache key từ sorted unique paths\n const cacheKey = `css:${uniquePaths.sort().join(',')}`;\n\n // Kiểm tra cache - return cached file if already bundled\n const cachedFileName = cssCache.get(cacheKey);\n if (cachedFileName) {\n const cachedFile = bundledFilesMap.get(cachedFileName);\n if (cachedFile) {\n return {\n hash: cachedFile.hash,\n filename: cachedFile.filename,\n content: cachedFile.content,\n };\n }\n }\n\n try {\n // Đọc và gộp tất cả CSS files từ dist/client\n const cssContents: string[] = [];\n\n for (const cssPath of uniquePaths) {\n // cssPath có thể là \"/assets/xxx.css\" hoặc từ manifest\n const filePath = cssPath.startsWith('/')\n ? path.join(distClientDir, cssPath.substring(1))\n : path.join(distClientDir, cssPath);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n cssContents.push(`/* ${cssPath} */\\n${content}\\n`);\n } catch (err) {\n console.warn(`[bundler] Failed to read CSS file: ${cssPath}`, err);\n }\n }\n\n const bundledContent = cssContents.join('\\n\\n');\n const hash = generateHash(bundledContent);\n const filename = `bundle-${hash}.css`;\n\n // Lưu vào map với key = filename\n const bundledFile: BundledFile = {\n content: bundledContent,\n hash,\n filename,\n mimeType: 'text/css',\n };\n bundledFilesMap.set(filename, bundledFile);\n\n // Cache filename for deduplication\n cssCache.set(cacheKey, filename);\n\n return { hash, filename, content: bundledContent };\n } catch (error) {\n console.error('[bundler] Error bundling CSS:', error);\n return { hash: '', filename: '', content: '' };\n }\n}\n\n/**\n * Get bundled file từ map\n */\nexport function getBundledFile(filename: string): BundledFile | undefined {\n return bundledFilesMap.get(filename);\n}\n\n/**\n * Clear bundled files map (useful for testing)\n */\nexport function clearBundledFiles(): void {\n bundledFilesMap.clear();\n}\n","import type { Request as ExpressRequest, Response as ExpressResponse } from 'express';\nimport { existsSync } from 'fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport requestIp from 'request-ip';\nimport serialize from 'serialize-javascript';\nimport type { ViteDevServer } from 'vite';\nimport { createContext, type MiddlewareHandler, type RewritePayload } from '../middleware';\nimport { bundleCss, bundleScripts, getBundledFile } from './bundler';\nimport type { RenderResult, RequestInfo } from './entry-server';\nimport { escapeProp } from './render';\nimport { createHeadersFromExpressRequest, parseCookies } from './request';\n\nexport interface ServerOptions {\n root?: string;\n port?: number;\n base?: string;\n publicDir?: string;\n setupApp?: (app: any) => void | Promise<void>;\n app?: any; // Express\n}\n\nexport interface ServerContext {\n app: any; // Express app\n vite?: ViteDevServer;\n}\n\n/**\n * Apply or replace lang attribute on <html> tag\n */\nexport function applyHtmlLang(template: string, lang: string): string {\n // Escape so a loader-supplied lang (possibly derived from user input) cannot\n // break out of the attribute / <html> tag (XSS). escapeProp handles \", <, >, &.\n const safeLang = escapeProp(lang);\n return template.replace(/<html\\b([^>]*)>/i, (match, attrs) => {\n // Check if lang already exists\n if (/\\blang\\s*=/i.test(attrs)) {\n // Replace existing lang value (function replacer avoids $-pattern issues)\n return match.replace(/lang\\s*=\\s*\"[^\"]*\"/i, () => `lang=\"${safeLang}\"`);\n } else {\n // Add lang attribute\n return `<html lang=\"${safeLang}\"${attrs}>`;\n }\n });\n}\n\ntype EntryServerModule = {\n render: (url: string, requestInfo?: RequestInfo) => Promise<RenderResult>;\n loadMiddleware?: () => Promise<MiddlewareHandler | undefined>;\n};\n\nfunction getRequestUrl(req: ExpressRequest): URL {\n return new URL(`${req.protocol}://${req.get('host')}${req.originalUrl}`);\n}\n\nfunction getRenderUrl(urlObject: URL, base: string): string {\n const requestPath = `${urlObject.pathname}${urlObject.search}`;\n return requestPath.replace(base, '') || '/';\n}\n\nfunction createWebRequestFromExpress(req: ExpressRequest): globalThis.Request {\n const init: RequestInit & { duplex?: 'half' } = {\n method: req.method,\n headers: createHeadersFromExpressRequest(req),\n };\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n init.body = req as unknown as BodyInit;\n init.duplex = 'half';\n }\n\n return new globalThis.Request(getRequestUrl(req).href, init);\n}\n\nfunction createRequestInfo(\n req: ExpressRequest,\n webRequest: globalThis.Request,\n base: string,\n locals: Record<string, unknown>,\n): RequestInfo {\n const urlObject = new URL(webRequest.url);\n const renderUrl = getRenderUrl(urlObject, base);\n const normalizedPath = renderUrl.startsWith('/') ? renderUrl : `/${renderUrl}`;\n const headers: Record<string, string> = {};\n webRequest.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n url: urlObject,\n path: normalizedPath,\n pathname: urlObject.pathname,\n method: webRequest.method,\n headers,\n cookies: parseCookies(webRequest.headers.get('cookie') ?? undefined),\n query: Object.fromEntries(urlObject.searchParams.entries()),\n ip: requestIp.getClientIp(req) ?? undefined,\n locals,\n };\n}\n\nfunction createRewriteRequest(\n payload: RewritePayload | undefined,\n currentRequest: globalThis.Request,\n currentUrl: URL,\n): globalThis.Request {\n if (!payload) {\n return currentRequest;\n }\n\n if (payload instanceof globalThis.Request) {\n return payload;\n }\n\n if (payload instanceof URL) {\n return new globalThis.Request(payload.href, currentRequest.clone());\n }\n\n return new globalThis.Request(new URL(payload, currentUrl).href, currentRequest.clone());\n}\n\nasync function sendWebResponse(\n req: ExpressRequest,\n res: ExpressResponse,\n response: globalThis.Response,\n): Promise<void> {\n res.status(response.status);\n const setCookieValues = getSetCookieHeaders(response.headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'set-cookie') {\n return;\n }\n res.setHeader(key, value);\n });\n if (setCookieValues.length > 0) {\n res.setHeader('Set-Cookie', setCookieValues);\n }\n\n if (req.method === 'HEAD') {\n res.end();\n return;\n }\n\n const body = Buffer.from(await response.arrayBuffer());\n res.send(body);\n}\n\nfunction getSetCookieHeaders(headers: Headers): string[] {\n const getSetCookie = (headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;\n if (typeof getSetCookie === 'function') {\n return getSetCookie.call(headers);\n }\n\n const raw = (headers as Headers & { raw?: () => Record<string, string[]> }).raw?.();\n if (raw?.['set-cookie']) {\n return raw['set-cookie'];\n }\n\n const value = headers.get('set-cookie');\n return value ? splitSetCookieHeader(value) : [];\n}\n\nfunction splitSetCookieHeader(value: string): string[] {\n const cookies: string[] = [];\n let start = 0;\n\n for (let i = 0; i < value.length; i++) {\n if (value[i] !== ',') continue;\n\n const rest = value.slice(i + 1);\n if (/^\\s*[^=;,]+=/.test(rest)) {\n cookies.push(value.slice(start, i).trim());\n start = i + 1;\n }\n }\n\n cookies.push(value.slice(start).trim());\n return cookies.filter(Boolean);\n}\n\nfunction createRawResponse(rendered: RenderResult): globalThis.Response | null {\n if (!rendered.rawResponse) {\n return null;\n }\n\n const { body, contentType, statusCode, headers } = rendered.rawResponse;\n const responseHeaders = new Headers(headers);\n responseHeaders.set('Content-Type', contentType);\n\n return new globalThis.Response(body as BodyInit, {\n status: statusCode || 200,\n headers: responseHeaders,\n });\n}\n\nasync function createPageResponse({\n rendered,\n template,\n manifest,\n root,\n distClientDir,\n isProduction,\n}: {\n rendered: RenderResult;\n template: string;\n manifest?: Record<string, any>;\n root: string;\n distClientDir: string;\n isProduction: boolean;\n}): Promise<globalThis.Response> {\n const rawResponse = createRawResponse(rendered);\n if (rawResponse) {\n return rawResponse;\n }\n\n if (rendered.redirect) {\n return new globalThis.Response(null, {\n status: rendered.redirect.statusCode,\n headers: {\n Location: rendered.redirect.url,\n },\n });\n }\n\n let scriptSrcList: string[] = rendered.scripts || [];\n let cssSrcList: string[] = rendered.styles || [];\n const islandEntries = rendered.islands || [];\n let cacheTags: string[] = rendered.cacheTags || [];\n const maxAge: number | undefined = rendered.maxAge;\n const sMaxAge: number | undefined = rendered.sMaxAge;\n const swr: number | undefined = rendered.swr;\n\n let extraHead = '';\n let globalScripts: string[] = [];\n let islandRegistryScript = '';\n\n if (isProduction && manifest) {\n scriptSrcList = scriptSrcList.filter((src) => !src.includes('.global.'));\n cssSrcList = cssSrcList.filter((src) => !src.includes('.global.'));\n\n const cssFiles = new Set<string>();\n const preloadFiles = new Set<string>();\n\n function collectFromEntry(entryKey: string): { file: string | null } {\n const entry = manifest![entryKey];\n if (!entry) return { file: null };\n\n if (entry.css) entry.css.forEach((css: string) => cssFiles.add(css));\n if (entry.imports) {\n entry.imports.forEach((importKey: string) => {\n const importedChunk = manifest![importKey];\n if (importedChunk?.file) preloadFiles.add(importedChunk.file);\n if (importedChunk?.css) {\n importedChunk.css.forEach((css: string) => cssFiles.add(css));\n }\n if (importedChunk?.imports) {\n importedChunk.imports.forEach((key: string) => {\n const chunk = manifest![key];\n if (chunk?.file) preloadFiles.add(chunk.file);\n if (chunk?.css) chunk.css.forEach((css: string) => cssFiles.add(css));\n });\n }\n });\n }\n\n return { file: entry.file };\n }\n\n const mappedScripts: string[] = [];\n for (const src of scriptSrcList) {\n const entryKey = src.replace(/^\\//, '');\n const { file } = collectFromEntry(entryKey);\n if (file) mappedScripts.push(`/${file}`);\n }\n\n const mappedCssFiles: string[] = [];\n for (const src of cssSrcList) {\n const entryKey = src.replace(/^\\//, '');\n const { file } = collectFromEntry(entryKey);\n if (file) {\n mappedCssFiles.push(`/${file}`);\n cssFiles.add(file);\n }\n }\n\n if (mappedScripts.length > 0) {\n const bundledScript = await bundleScripts(mappedScripts, root, distClientDir);\n scriptSrcList = bundledScript.filename ? [`/${bundledScript.filename}`] : mappedScripts;\n }\n\n if (mappedCssFiles.length > 0) {\n const bundledCss = await bundleCss(mappedCssFiles, root, distClientDir);\n if (bundledCss.filename) {\n cssSrcList = [`/${bundledCss.filename}`];\n }\n }\n\n const globalEntry = manifest['src/client.global.ts'];\n if (globalEntry) {\n if (globalEntry.css && globalEntry.css.length > 0) {\n globalEntry.css.forEach((cssFile: string) => {\n extraHead += `<link rel=\"stylesheet\" crossorigin href=\"/${cssFile}\">`;\n });\n }\n if (globalEntry.file) {\n globalScripts.push(`/${globalEntry.file}`);\n }\n }\n\n if (islandEntries.length > 0) {\n const islandMap: Record<string, string> = {};\n for (const island of islandEntries) {\n const entry = manifest[island.src];\n if (entry?.file) {\n islandMap[island.key] = `/${entry.file}`;\n }\n }\n if (Object.keys(islandMap).length > 0) {\n islandRegistryScript = `<script>window.__L5E_ISLANDS__=${serialize(islandMap)}</script>`;\n }\n }\n\n if (cssSrcList.length > 0) {\n extraHead += cssSrcList\n .map((file) => `<link rel=\"stylesheet\" crossorigin href=\"${file}\">`)\n .join('');\n }\n }\n\n let cssHtml = '';\n if (!isProduction) {\n cssHtml = cssSrcList.map((src) => `<link rel=\"stylesheet\" href=\"${src}\">`).join('');\n }\n\n let allScripts = [...globalScripts, ...scriptSrcList];\n\n if (!isProduction) {\n const globalTsPath = path.join(root, 'src', 'client.global.ts');\n if (existsSync(globalTsPath)) {\n allScripts = ['/src/client.global.ts', ...allScripts];\n }\n\n if (islandEntries.length > 0) {\n const islandMap: Record<string, string> = {};\n for (const island of islandEntries) {\n islandMap[island.key] = `/${island.src}`;\n }\n islandRegistryScript = `<script>window.__L5E_ISLANDS__=${serialize(islandMap)}</script>`;\n }\n }\n\n const scriptsHtml =\n islandRegistryScript +\n allScripts.map((src) => `<script type=\"module\" src=\"${src}\"></script>`).join('');\n\n const templateWithLang = rendered.lang ? applyHtmlLang(template, rendered.lang) : template;\n\n // Replacer fns (not strings) so `$`-sequences in rendered HTML — e.g. an SSR\n // island body or user text containing \"$$\" / \"$&\" — aren't interpreted as\n // special String.replace replacement patterns.\n const html = rendered.rawHtml\n ? rendered.html || ''\n : templateWithLang\n .replace(`<!--app-head-->`, () => (rendered.head ?? '') + extraHead + cssHtml)\n .replace(`<!--app-html-->`, () => rendered.html ?? '')\n .replace(`<!--app-scripts-->`, () => scriptsHtml);\n\n const headers = new Headers({\n 'Content-Type': 'text/html',\n });\n\n const cacheControlParts: string[] = ['public'];\n if (maxAge !== undefined) cacheControlParts.push(`max-age=${maxAge}`);\n if (sMaxAge !== undefined) cacheControlParts.push(`s-maxage=${sMaxAge}`);\n if (swr !== undefined) cacheControlParts.push(`stale-while-revalidate=${swr}`);\n\n if (cacheControlParts.length > 1 && isProduction) {\n headers.set('Cache-Control', cacheControlParts.join(', '));\n }\n\n if (process.env.NODE_ENV === 'production') {\n cacheTags = optimizeCacheTags(cacheTags);\n }\n headers.set('Cache-Tag', ['global', ...cacheTags].join(','));\n\n return new globalThis.Response(html, {\n status: rendered.statusCode || 200,\n headers,\n });\n}\n\nexport async function createServer(options: ServerOptions = {}): Promise<ServerContext> {\n const root = options.root || process.cwd();\n const base = options.base || '/';\n const isProduction = process.env.NODE_ENV === 'production';\n\n // Cached production assets\n const templateHtml = isProduction\n ? await fs.readFile(path.join(root, './index.html'), 'utf-8')\n : '';\n\n // Create http server\n // @ts-ignore\n const express = (await import('express')).default;\n const app = options.app || express();\n\n // Serve static files from public directory\n if (options.publicDir) {\n const publicPath = path.isAbsolute(options.publicDir)\n ? options.publicDir\n : path.join(root, options.publicDir);\n\n if (existsSync(publicPath)) {\n app.use(express.static(publicPath));\n }\n }\n\n // Add Vite or respective production middlewares\n let vite: ViteDevServer | undefined;\n const distClientDir = path.join(root, './dist/client');\n\n if (!isProduction) {\n const { createServer } = await import('vite');\n const configFile = path.join(root, 'vite.config.js');\n vite = await createServer({\n root,\n configFile,\n server: { middlewareMode: true },\n appType: 'custom',\n base,\n optimizeDeps: {\n exclude: ['@withl5e/l5e', 'file-type'],\n },\n ssr: {\n resolve: {\n conditions: ['development', 'default'],\n },\n },\n resolve: {\n conditions: ['development', 'default'],\n },\n });\n app.use(vite.middlewares);\n } else {\n // @ts-ignore\n const compression = (await import('compression')).default;\n // @ts-ignore\n const sirv = (await import('sirv')).default;\n app.use(compression());\n app.use(base, sirv(distClientDir, { extensions: [] }));\n\n // Route để serve bundled files từ memory map\n // Äặt route nà y trước route HTML để catch request trước\n app.get(\n `${base === '/' ? '' : base}/bundle-:hash.:ext`,\n async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const { hash, ext } = req.params;\n const filename = `bundle-${hash}.${ext}`;\n const bundledFile = getBundledFile(filename);\n\n if (!bundledFile) {\n return res.status(404).send('Bundled file not found');\n }\n\n res.set({\n 'Content-Type': bundledFile.mimeType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n res.send(bundledFile.content);\n } catch (e: any) {\n console.error('[server] Error serving bundled file:', e);\n res.status(500).end('Internal server error');\n }\n },\n );\n }\n\n // Action routes — JSON body parsing scoped to action endpoints only\n app.use('/_l5e/action', express.json({ limit: '100kb' }));\n\n // Validate action key format: actionName_hexHash\n const ACTION_KEY_RE = /^[a-zA-Z]\\w+_[0-9a-f]{1,4}$/;\n\n // Load action registry and viewActions glob from virtual module (dev) or built bundle (prod)\n let prodActionRegistry: Record<string, { modulePath: string; actionName: string }> | null = null;\n let prodViewActions: Record<string, () => Promise<any>> | null = null;\n\n async function getActionRegistry(): Promise<\n Record<string, { modulePath: string; actionName: string }>\n > {\n if (!isProduction) {\n const mod = await vite!.ssrLoadModule('virtual:l5e-actions');\n return mod.actionRegistry || {};\n }\n if (!prodActionRegistry) {\n const registryPath = path.join(root, './dist/server/action-registry.json');\n const json = await fs.readFile(registryPath, 'utf-8');\n prodActionRegistry = JSON.parse(json);\n }\n return prodActionRegistry!;\n }\n\n async function getViewActions(): Promise<Record<string, () => Promise<any>>> {\n if (!isProduction) {\n const mod = await vite!.ssrLoadModule('virtual:l5e-actions');\n return mod.viewActions || {};\n }\n if (!prodViewActions) {\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const mod = await import(pathToFileURL(entryServerPath).href);\n prodViewActions = mod.viewActions || {};\n }\n return prodViewActions!;\n }\n\n // Action route handler — hashed action keys\n // URL: /_l5e/action/:actionKey (e.g., /_l5e/action/loadMoreComments_a1b2)\n app.all('/_l5e/action/:actionKey', async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const { actionKey } = req.params;\n\n // Validate action key format\n if (!ACTION_KEY_RE.test(actionKey)) {\n return res.status(400).send('Invalid action key');\n }\n\n // Look up action in registry\n const registry = await getActionRegistry();\n const entry = registry[actionKey];\n if (!entry) {\n return res.status(404).send('Action not found');\n }\n\n const { modulePath, actionName } = entry;\n\n // Import action module via viewActions glob (works in both dev and prod)\n let actionModule: any;\n if (!isProduction) {\n try {\n actionModule = await vite!.ssrLoadModule(`/src/${modulePath}/actions.tsx`);\n } catch {\n actionModule = await vite!.ssrLoadModule(`/src/${modulePath}/actions.ts`);\n }\n } else {\n const viewActions = await getViewActions();\n // Find matching glob entry by modulePath\n const globKey = viewActions[`/src/${modulePath}/actions.tsx`]\n ? `/src/${modulePath}/actions.tsx`\n : viewActions[`/src/${modulePath}/actions.ts`]\n ? `/src/${modulePath}/actions.ts`\n : null;\n if (!globKey) {\n return res.status(404).send('Action module not found');\n }\n actionModule = await viewActions[globKey]();\n }\n\n // Look up exported action — use hasOwnProperty to avoid prototype pollution\n if (!Object.prototype.hasOwnProperty.call(actionModule, actionName)) {\n return res.status(404).send('Action not found');\n }\n const action = actionModule[actionName];\n if (!action || !action.handler) {\n return res.status(404).send('Action not found');\n }\n\n // Enforce the action's declared HTTP method. Previously `app.all` accepted\n // any method and `action.method` was ignored, so a state-changing POST\n // action could be triggered via GET (e.g. <img src>) — a CSRF vector.\n const allowedMethod = (action.method || 'GET').toUpperCase();\n if (req.method.toUpperCase() !== allowedMethod) {\n return res.status(405).set('Allow', allowedMethod).send('Method Not Allowed');\n }\n\n // Build RequestInfo (same pattern as HTML handler)\n const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;\n const urlObject = new URL(fullUrl);\n\n const requestInfo = {\n url: urlObject,\n path: req.originalUrl,\n pathname: urlObject.pathname,\n method: req.method,\n headers: req.headers,\n cookies: parseCookies(req.headers.cookie as string),\n query: req.query || {},\n body: req.body,\n ip: requestIp.getClientIp(req),\n };\n\n // Import render utilities from entry-server (bundled in SSR build)\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const { runInRenderContext } = await (isProduction\n ? import(pathToFileURL(entryServerPath).href)\n : vite!.ssrLoadModule('@withl5e/l5e/jsx-runtime'));\n const { renderJsxToHtmlString } = await (isProduction\n ? import(pathToFileURL(entryServerPath).href)\n : vite!.ssrLoadModule('@withl5e/l5e'));\n\n // Run action handler in render context (needed for JSX)\n const html = await runInRenderContext(\n async () => {\n const jsx = await action.handler(requestInfo);\n return renderJsxToHtmlString(jsx);\n },\n requestInfo,\n modulePath,\n );\n\n res.set('Content-Type', 'text/html').send(html);\n } catch (e: any) {\n vite?.ssrFixStacktrace?.(e);\n console.error('[l5e] Action error:', e.stack || e);\n res.status(500).send('Internal server error');\n }\n });\n\n // Serve HTML\n app.use(async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const url = req.originalUrl.replace(base, '');\n\n let template: string;\n let render: (url: string, requestInfo?: any) => Promise<any>;\n let loadMiddleware: EntryServerModule['loadMiddleware'];\n let manifest: Record<string, any> | undefined;\n\n if (!isProduction) {\n // Always read fresh template in development\n template = await fs.readFile(path.join(root, './index.html'), 'utf-8');\n template = await vite!.transformIndexHtml(url, template);\n\n // Inject Vite HMR client for hot reload\n if (!template.includes('@vite/client')) {\n template = template.replace(\n '</head>',\n '<script type=\"module\" src=\"/@vite/client\"></script></head>',\n );\n }\n\n const entryServer = (await vite!.ssrLoadModule(\n '@withl5e/l5e/entry-server',\n )) as EntryServerModule;\n render = entryServer.render;\n loadMiddleware = entryServer.loadMiddleware;\n } else {\n template = templateHtml;\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const entryServer = (await import(\n pathToFileURL(entryServerPath).href\n )) as EntryServerModule;\n render = entryServer.render;\n loadMiddleware = entryServer.loadMiddleware;\n // Read manifest to map hashed assets\n const manifestJson = await fs.readFile(\n path.join(root, './dist/client/.vite/manifest.json'),\n 'utf-8',\n );\n manifest = JSON.parse(manifestJson);\n }\n\n const loadedMiddleware = await loadMiddleware?.();\n const handler: MiddlewareHandler =\n typeof loadedMiddleware === 'function' ? loadedMiddleware : (_ctx, next) => next();\n\n const locals: Record<string, unknown> = {};\n const initialRequest = createWebRequestFromExpress(req);\n const context = createContext({\n request: initialRequest,\n requestInfo: createRequestInfo(req, initialRequest, base, locals),\n locals,\n clientAddress: requestIp.getClientIp(req),\n });\n\n const renderResponse = async (webRequest: globalThis.Request) => {\n const nextRequestInfo = createRequestInfo(req, webRequest, base, locals);\n const nextUrl = getRenderUrl(nextRequestInfo.url!, base);\n const nextRendered = await render(nextUrl, nextRequestInfo);\n return createPageResponse({\n rendered: nextRendered,\n template,\n manifest,\n root,\n distClientDir,\n isProduction,\n });\n };\n\n const next = async (payload?: RewritePayload) => {\n const nextRequest = createRewriteRequest(payload, context.request, context.url);\n context.request = nextRequest;\n context.url = new URL(nextRequest.url);\n context.cookies = parseCookies(nextRequest.headers.get('cookie') ?? undefined);\n context.requestInfo = createRequestInfo(req, nextRequest, base, locals);\n return renderResponse(nextRequest);\n };\n\n context.rewrite = (payload: RewritePayload) => next(payload);\n\n const response = await handler(context, next);\n await sendWebResponse(req, res, response);\n } catch (e: any) {\n vite?.ssrFixStacktrace?.(e);\n console.error(e.stack);\n // Never leak stack traces to the client in production (info disclosure).\n res.status(500).end(isProduction ? 'Internal Server Error' : e.stack);\n }\n });\n\n return { app, vite };\n}\n\nexport async function startServer(options: ServerOptions = {}): Promise<void> {\n const port = options.port || 5173;\n\n // Create Express app first\n // @ts-ignore\n const express = (await import('express')).default;\n const app = express();\n\n // Call callback if provided to allow custom routes before setting up L5E server\n if (options.setupApp) {\n console.log('setupApp');\n await options.setupApp(app);\n }\n\n const { app: serverApp } = await createServer({ ...options, app });\n\n serverApp.listen(port, () => {\n console.log(`Server started at http://localhost:${port}`);\n });\n}\n\nconst MAX_TAGS = 1000;\n\nexport function hashTag(tag: string): string {\n // global tag is not hashed, better for ci/cd\n if (tag === 'global') {\n return 'global';\n }\n\n let hash = 0;\n for (let i = 0; i < tag.length; i++) {\n const char = tag.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32bit integer\n }\n return Math.abs(hash).toString(36).substring(0, 8);\n}\n\nexport function optimizeCacheTags(tags: Set<string> | string[]): string[] {\n const _tags = Array.isArray(tags) ? tags : [...tags];\n const result = _tags.slice(0, MAX_TAGS).map(hashTag);\n return result;\n}\n"],"names":["rollupModulePromise","loadRollup","require","createRequire","vitePath","rollupPath","path","pathToFileURL","bundledFilesMap","bundleCache","cssCache","generateHash","content","createHash","bundleScripts","scriptPaths","rootDir","distClientDir","uniquePaths","cacheKey","cachedEntryFileName","entryFile","hash","tempDir","fs","entryContent","p","i","filePath","rollupOptions","source","importer","_options","resolved","id","outputOptions","rollup","bundle","output","o","bundledFile","entryChunk","error","bundleCss","cssPaths","cachedFileName","cachedFile","cssContents","cssPath","err","bundledContent","filename","getBundledFile","applyHtmlLang","template","lang","safeLang","escapeProp","match","attrs","getRequestUrl","req","getRenderUrl","urlObject","base","createWebRequestFromExpress","init","createHeadersFromExpressRequest","createRequestInfo","webRequest","locals","renderUrl","normalizedPath","headers","value","key","parseCookies","requestIp","createRewriteRequest","payload","currentRequest","currentUrl","sendWebResponse","res","response","setCookieValues","getSetCookieHeaders","body","getSetCookie","raw","splitSetCookieHeader","cookies","start","rest","createRawResponse","rendered","contentType","statusCode","responseHeaders","createPageResponse","manifest","root","isProduction","rawResponse","scriptSrcList","cssSrcList","islandEntries","cacheTags","maxAge","sMaxAge","swr","extraHead","globalScripts","islandRegistryScript","collectFromEntry","entryKey","entry","css","cssFiles","importKey","importedChunk","preloadFiles","chunk","src","mappedScripts","file","mappedCssFiles","bundledScript","bundledCss","globalEntry","cssFile","islandMap","island","serialize","cssHtml","allScripts","globalTsPath","existsSync","scriptsHtml","templateWithLang","html","cacheControlParts","optimizeCacheTags","createServer","options","templateHtml","express","app","publicPath","vite","compression","sirv","ext","e","configFile","ACTION_KEY_RE","prodActionRegistry","prodViewActions","getActionRegistry","registryPath","json","getViewActions","entryServerPath","actionKey","modulePath","actionName","actionModule","viewActions","globKey","action","allowedMethod","fullUrl","requestInfo","runInRenderContext","renderJsxToHtmlString","jsx","url","render","loadMiddleware","entryServer","manifestJson","loadedMiddleware","handler","_ctx","next","initialRequest","context","createContext","renderResponse","nextRequestInfo","nextUrl","nextRendered","nextRequest","startServer","port","serverApp","MAX_TAGS","hashTag","tag","char","tags"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../src/core/bundler.ts","../src/core/server.ts"],"sourcesContent":["/// <reference path=\"./jsx-types.d.ts\" />\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { OutputOptions, RollupOptions } from 'rollup';\n\nlet rollupModulePromise: Promise<typeof import('rollup')> | null = null;\n\n/**\n * Resolve rollup lazily. We can't `import 'rollup'` directly because when the\n * framework gets bundled into the consumer's SSR output the bare specifier is\n * hoisted to a static import that pnpm doesn't satisfy. Resolve through `vite`\n * instead — vite is a peer dep so it's always installed, and it always ships\n * rollup as a direct dependency.\n */\nfunction loadRollup(): Promise<typeof import('rollup')> {\n if (!rollupModulePromise) {\n rollupModulePromise = (async () => {\n const require = createRequire(import.meta.url);\n const vitePath = require.resolve('vite');\n const rollupPath = require.resolve('rollup', { paths: [path.dirname(vitePath)] });\n return (await import(pathToFileURL(rollupPath).href)) as typeof import('rollup');\n })();\n }\n return rollupModulePromise;\n}\n\ninterface BundledFile {\n content: string;\n hash: string;\n filename: string;\n mimeType: string;\n}\n\n// Memory map để lưu bundled files\nconst bundledFilesMap = new Map<string, BundledFile>();\n\n// Cache map để deduplicate bundling requests (cacheKey → entry chunk fileName)\nconst bundleCache = new Map<string, string>();\nconst cssCache = new Map<string, string>();\n\n/**\n * Generate hash từ content\n */\nfunction generateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex').substring(0, 16);\n}\n\n/**\n * Bundle JavaScript files từ dist/client thành 1 file\n * Trong production, các file đã được build sẵn trong dist/client\n */\nexport async function bundleScripts(\n scriptPaths: string[],\n rootDir: string,\n distClientDir: string,\n): Promise<{ hash: string; filename: string; content: string }> {\n if (scriptPaths.length === 0) {\n return { hash: '', filename: '', content: '' };\n }\n\n // Dedupe paths (remove duplicates)\n const uniquePaths = [...new Set(scriptPaths)];\n\n // Tạo cache key từ sorted unique paths\n const cacheKey = `scripts:${uniquePaths.sort().join(',')}`;\n\n // Kiểm tra cache - return entry chunk info if already bundled\n const cachedEntryFileName = bundleCache.get(cacheKey);\n if (cachedEntryFileName) {\n const entryFile = bundledFilesMap.get(cachedEntryFileName);\n if (entryFile) {\n return {\n hash: entryFile.hash,\n filename: entryFile.filename,\n content: entryFile.content,\n };\n }\n }\n\n // Temp file path for cleanup\n let entryFile: string | null = null;\n\n try {\n // Sử dụng rollup để bundle nếu cần (resolve imports, etc)\n // Tạo temp entry file\n const hash = generateHash(uniquePaths.join('\\n'));\n const tempDir = path.join(rootDir, '.temp-bundle');\n await fs.mkdir(tempDir, { recursive: true }).catch(() => {});\n\n entryFile = path.join(tempDir, `entry-${hash}.js`);\n // Tạo entry file import tất cả scripts\n const entryContent = uniquePaths\n .map((p, i) => {\n const filePath = p.startsWith('/')\n ? path.join(distClientDir, p.substring(1))\n : path.join(distClientDir, p);\n return `import ${JSON.stringify(filePath)};`;\n })\n .join('\\n');\n\n await fs.writeFile(entryFile, entryContent, 'utf-8');\n console.log(`[bundler] Wrote entry file to ${entryFile}`);\n console.log(`[bundler] Entry content: ${entryContent}`);\n // Rollup config để bundle\n const rollupOptions: RollupOptions = {\n input: entryFile,\n plugins: [\n {\n name: 'vendor-path-rewriter',\n resolveId(source, importer, _options) {\n // Handle vendor/chunk/global files: convert absolute paths to web paths\n // Global files (*.global.*) are already loaded by client.global.ts —\n // re-bundling them would create duplicate module instances (e.g. nanostores)\n if (\n source.includes('vendor-') ||\n source.includes('chunk-') ||\n source.includes('.global')\n ) {\n console.log(`[bundler] Resolving source: ${source}`);\n if (path.isAbsolute(source)) {\n console.log(`[bundler] Resolving absolute path: ${source}`);\n // e.g., C:\\...\\dist\\client\\assets\\vendor-react-XXX.js -> /assets/vendor-react-XXX.js\n const relativePath = path.relative(distClientDir, source);\n const webPath = '/' + relativePath.replace(/\\\\/g, '/');\n return { id: webPath, external: true };\n } else if (importer && source.startsWith('.')) {\n console.log(\n `[bundler] Resolving relative path: ${source} from importer: ${importer}`,\n );\n // Relative path like ./auth.global-BOVr81Z5.js — resolve from importer\n const resolved = path.resolve(path.dirname(importer), source);\n const relativePath = path.relative(distClientDir, resolved);\n const webPath = '/' + relativePath.replace(/\\\\/g, '/');\n return { id: webPath, external: true };\n } else {\n console.log(`[bundler] Resolving source: ${source}`);\n }\n }\n return null; // Let other plugins/external handle\n },\n },\n ],\n external: (id) => {\n // External node_modules\n if (!id.startsWith('.') && !path.isAbsolute(id)) {\n return true;\n }\n\n // Let plugin handle vendor/chunk/global files (don't mark external here)\n if (id.includes('vendor-') || id.includes('chunk-') || id.includes('.global')) {\n return false; // Let plugin's resolveId handle path rewriting\n }\n\n return false;\n },\n };\n\n const outputOptions: OutputOptions = {\n format: 'es',\n inlineDynamicImports: false,\n entryFileNames: 'bundle-[hash].js',\n chunkFileNames: 'bundle-[hash].js',\n };\n\n const { rollup } = await loadRollup();\n const bundle = await rollup(rollupOptions);\n const { output } = await bundle.generate(outputOptions);\n await bundle.close();\n\n // Lấy bundled content từ rollup\n\n output.forEach((o) => {\n if (o.type !== 'chunk') {\n return;\n }\n // Lưu vào map với key = fileName\n const bundledFile: BundledFile = {\n content: o.code || '',\n hash: generateHash(o.code || ''),\n filename: o.fileName,\n mimeType: 'application/javascript',\n };\n bundledFilesMap.set(o.fileName, bundledFile);\n });\n\n // Cache entry chunk fileName for deduplication\n const entryChunk = output[0];\n if (entryChunk?.type === 'chunk') {\n bundleCache.set(cacheKey, entryChunk.fileName);\n }\n\n // Return entry chunk info\n return {\n hash: generateHash(output[0]?.code || ''),\n filename: output[0]?.fileName || '',\n content: output[0]?.code || '',\n };\n } catch (error) {\n console.error('[bundler] Error bundling scripts:', error);\n return { hash: '', filename: '', content: '' };\n } finally {\n // Cleanup temp entry file\n if (entryFile) {\n await fs.unlink(entryFile).catch(() => {\n // Ignore cleanup errors\n });\n }\n }\n}\n\n/**\n * Bundle CSS files từ dist/client thành 1 file\n * Trong production, các file đã được build sẵn trong dist/client\n */\nexport async function bundleCss(\n cssPaths: string[],\n rootDir: string,\n distClientDir: string,\n): Promise<{ hash: string; filename: string; content: string }> {\n if (cssPaths.length === 0) {\n return { hash: '', filename: '', content: '' };\n }\n\n // Dedupe paths (remove duplicates)\n const uniquePaths = [...new Set(cssPaths)];\n\n // Tạo cache key từ sorted unique paths\n const cacheKey = `css:${uniquePaths.sort().join(',')}`;\n\n // Kiểm tra cache - return cached file if already bundled\n const cachedFileName = cssCache.get(cacheKey);\n if (cachedFileName) {\n const cachedFile = bundledFilesMap.get(cachedFileName);\n if (cachedFile) {\n return {\n hash: cachedFile.hash,\n filename: cachedFile.filename,\n content: cachedFile.content,\n };\n }\n }\n\n try {\n // Đọc và gộp tất cả CSS files từ dist/client\n const cssContents: string[] = [];\n\n for (const cssPath of uniquePaths) {\n // cssPath có thể là \"/assets/xxx.css\" hoặc từ manifest\n const filePath = cssPath.startsWith('/')\n ? path.join(distClientDir, cssPath.substring(1))\n : path.join(distClientDir, cssPath);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n cssContents.push(`/* ${cssPath} */\\n${content}\\n`);\n } catch (err) {\n console.warn(`[bundler] Failed to read CSS file: ${cssPath}`, err);\n }\n }\n\n const bundledContent = cssContents.join('\\n\\n');\n const hash = generateHash(bundledContent);\n const filename = `bundle-${hash}.css`;\n\n // Lưu vào map với key = filename\n const bundledFile: BundledFile = {\n content: bundledContent,\n hash,\n filename,\n mimeType: 'text/css',\n };\n bundledFilesMap.set(filename, bundledFile);\n\n // Cache filename for deduplication\n cssCache.set(cacheKey, filename);\n\n return { hash, filename, content: bundledContent };\n } catch (error) {\n console.error('[bundler] Error bundling CSS:', error);\n return { hash: '', filename: '', content: '' };\n }\n}\n\n/**\n * Get bundled file từ map\n */\nexport function getBundledFile(filename: string): BundledFile | undefined {\n return bundledFilesMap.get(filename);\n}\n\n/**\n * Clear bundled files map (useful for testing)\n */\nexport function clearBundledFiles(): void {\n bundledFilesMap.clear();\n}\n","import type { Request as ExpressRequest, Response as ExpressResponse } from 'express';\nimport { existsSync } from 'fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport requestIp from 'request-ip';\nimport serialize from 'serialize-javascript';\nimport type { ViteDevServer } from 'vite';\nimport { createContext, type MiddlewareHandler, type RewritePayload } from '../middleware';\nimport { bundleCss, bundleScripts, getBundledFile } from './bundler';\nimport type { RenderResult, RequestInfo } from './entry-server';\nimport { escapeProp } from './render';\nimport { createHeadersFromExpressRequest, parseCookies } from './request';\n\nexport interface ServerOptions {\n root?: string;\n port?: number;\n base?: string;\n publicDir?: string;\n setupApp?: (app: any) => void | Promise<void>;\n app?: any; // Express\n}\n\nexport interface ServerContext {\n app: any; // Express app\n vite?: ViteDevServer;\n}\n\n/**\n * Apply or replace lang attribute on <html> tag\n */\nexport function applyHtmlLang(template: string, lang: string): string {\n // Escape so a loader-supplied lang (possibly derived from user input) cannot\n // break out of the attribute / <html> tag (XSS). escapeProp handles \", <, >, &.\n const safeLang = escapeProp(lang);\n return template.replace(/<html\\b([^>]*)>/i, (match, attrs) => {\n // Check if lang already exists\n if (/\\blang\\s*=/i.test(attrs)) {\n // Replace existing lang value (function replacer avoids $-pattern issues)\n return match.replace(/lang\\s*=\\s*\"[^\"]*\"/i, () => `lang=\"${safeLang}\"`);\n } else {\n // Add lang attribute\n return `<html lang=\"${safeLang}\"${attrs}>`;\n }\n });\n}\n\ntype EntryServerModule = {\n render: (url: string, requestInfo?: RequestInfo) => Promise<RenderResult>;\n loadMiddleware?: () => Promise<MiddlewareHandler | undefined>;\n};\n\nfunction getRequestUrl(req: ExpressRequest): URL {\n return new URL(`${req.protocol}://${req.get('host')}${req.originalUrl}`);\n}\n\nfunction getRenderUrl(urlObject: URL, base: string): string {\n const requestPath = `${urlObject.pathname}${urlObject.search}`;\n return requestPath.replace(base, '') || '/';\n}\n\nfunction createWebRequestFromExpress(req: ExpressRequest): globalThis.Request {\n const init: RequestInit & { duplex?: 'half' } = {\n method: req.method,\n headers: createHeadersFromExpressRequest(req),\n };\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n init.body = req as unknown as BodyInit;\n init.duplex = 'half';\n }\n\n return new globalThis.Request(getRequestUrl(req).href, init);\n}\n\nfunction createRequestInfo(\n req: ExpressRequest,\n webRequest: globalThis.Request,\n base: string,\n locals: Record<string, unknown>,\n): RequestInfo {\n const urlObject = new URL(webRequest.url);\n const renderUrl = getRenderUrl(urlObject, base);\n const normalizedPath = renderUrl.startsWith('/') ? renderUrl : `/${renderUrl}`;\n const headers: Record<string, string> = {};\n webRequest.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n url: urlObject,\n path: normalizedPath,\n pathname: urlObject.pathname,\n method: webRequest.method,\n headers,\n cookies: parseCookies(webRequest.headers.get('cookie') ?? undefined),\n query: Object.fromEntries(urlObject.searchParams.entries()),\n ip: requestIp.getClientIp(req) ?? undefined,\n locals,\n };\n}\n\nfunction createRewriteRequest(\n payload: RewritePayload | undefined,\n currentRequest: globalThis.Request,\n currentUrl: URL,\n): globalThis.Request {\n if (!payload) {\n return currentRequest;\n }\n\n if (payload instanceof globalThis.Request) {\n return payload;\n }\n\n if (payload instanceof URL) {\n return new globalThis.Request(payload.href, currentRequest.clone());\n }\n\n return new globalThis.Request(new URL(payload, currentUrl).href, currentRequest.clone());\n}\n\nasync function sendWebResponse(\n req: ExpressRequest,\n res: ExpressResponse,\n response: globalThis.Response,\n): Promise<void> {\n res.status(response.status);\n const setCookieValues = getSetCookieHeaders(response.headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'set-cookie') {\n return;\n }\n res.setHeader(key, value);\n });\n if (setCookieValues.length > 0) {\n res.setHeader('Set-Cookie', setCookieValues);\n }\n\n if (req.method === 'HEAD') {\n res.end();\n return;\n }\n\n const body = Buffer.from(await response.arrayBuffer());\n res.send(body);\n}\n\nfunction getSetCookieHeaders(headers: Headers): string[] {\n const getSetCookie = (headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;\n if (typeof getSetCookie === 'function') {\n return getSetCookie.call(headers);\n }\n\n const raw = (headers as Headers & { raw?: () => Record<string, string[]> }).raw?.();\n if (raw?.['set-cookie']) {\n return raw['set-cookie'];\n }\n\n const value = headers.get('set-cookie');\n return value ? splitSetCookieHeader(value) : [];\n}\n\nfunction splitSetCookieHeader(value: string): string[] {\n const cookies: string[] = [];\n let start = 0;\n\n for (let i = 0; i < value.length; i++) {\n if (value[i] !== ',') continue;\n\n const rest = value.slice(i + 1);\n if (/^\\s*[^=;,]+=/.test(rest)) {\n cookies.push(value.slice(start, i).trim());\n start = i + 1;\n }\n }\n\n cookies.push(value.slice(start).trim());\n return cookies.filter(Boolean);\n}\n\nfunction createRawResponse(rendered: RenderResult): globalThis.Response | null {\n if (!rendered.rawResponse) {\n return null;\n }\n\n const { body, contentType, statusCode, headers } = rendered.rawResponse;\n const responseHeaders = new Headers(headers);\n responseHeaders.set('Content-Type', contentType);\n\n return new globalThis.Response(body as BodyInit, {\n status: statusCode || 200,\n headers: responseHeaders,\n });\n}\n\nasync function createPageResponse({\n rendered,\n template,\n manifest,\n root,\n distClientDir,\n isProduction,\n}: {\n rendered: RenderResult;\n template: string;\n manifest?: Record<string, any>;\n root: string;\n distClientDir: string;\n isProduction: boolean;\n}): Promise<globalThis.Response> {\n const rawResponse = createRawResponse(rendered);\n if (rawResponse) {\n return rawResponse;\n }\n\n if (rendered.redirect) {\n return new globalThis.Response(null, {\n status: rendered.redirect.statusCode,\n headers: {\n Location: rendered.redirect.url,\n },\n });\n }\n\n let scriptSrcList: string[] = rendered.scripts || [];\n let cssSrcList: string[] = rendered.styles || [];\n const islandEntries = rendered.islands || [];\n let cacheTags: string[] = rendered.cacheTags || [];\n const maxAge: number | undefined = rendered.maxAge;\n const sMaxAge: number | undefined = rendered.sMaxAge;\n const swr: number | undefined = rendered.swr;\n\n let extraHead = '';\n let globalScripts: string[] = [];\n let islandRegistryScript = '';\n\n if (isProduction && manifest) {\n scriptSrcList = scriptSrcList.filter((src) => !src.includes('.global.'));\n cssSrcList = cssSrcList.filter((src) => !src.includes('.global.'));\n\n const cssFiles = new Set<string>();\n const preloadFiles = new Set<string>();\n\n function collectFromEntry(entryKey: string): { file: string | null } {\n const entry = manifest![entryKey];\n if (!entry) return { file: null };\n\n if (entry.css) entry.css.forEach((css: string) => cssFiles.add(css));\n if (entry.imports) {\n entry.imports.forEach((importKey: string) => {\n const importedChunk = manifest![importKey];\n if (importedChunk?.file) preloadFiles.add(importedChunk.file);\n if (importedChunk?.css) {\n importedChunk.css.forEach((css: string) => cssFiles.add(css));\n }\n if (importedChunk?.imports) {\n importedChunk.imports.forEach((key: string) => {\n const chunk = manifest![key];\n if (chunk?.file) preloadFiles.add(chunk.file);\n if (chunk?.css) chunk.css.forEach((css: string) => cssFiles.add(css));\n });\n }\n });\n }\n\n return { file: entry.file };\n }\n\n const mappedScripts: string[] = [];\n for (const src of scriptSrcList) {\n const entryKey = src.replace(/^\\//, '');\n const { file } = collectFromEntry(entryKey);\n if (file) mappedScripts.push(`/${file}`);\n }\n\n const mappedCssFiles: string[] = [];\n for (const src of cssSrcList) {\n const entryKey = src.replace(/^\\//, '');\n const { file } = collectFromEntry(entryKey);\n if (file) {\n mappedCssFiles.push(`/${file}`);\n cssFiles.add(file);\n }\n }\n\n if (mappedScripts.length > 0) {\n const bundledScript = await bundleScripts(mappedScripts, root, distClientDir);\n scriptSrcList = bundledScript.filename ? [`/${bundledScript.filename}`] : mappedScripts;\n }\n\n if (mappedCssFiles.length > 0) {\n const bundledCss = await bundleCss(mappedCssFiles, root, distClientDir);\n if (bundledCss.filename) {\n cssSrcList = [`/${bundledCss.filename}`];\n }\n }\n\n const globalEntry = manifest['src/client.global.ts'];\n if (globalEntry) {\n if (globalEntry.css && globalEntry.css.length > 0) {\n globalEntry.css.forEach((cssFile: string) => {\n extraHead += `<link rel=\"stylesheet\" crossorigin href=\"/${cssFile}\">`;\n });\n }\n if (globalEntry.file) {\n globalScripts.push(`/${globalEntry.file}`);\n }\n }\n\n if (islandEntries.length > 0) {\n const islandMap: Record<string, string> = {};\n for (const island of islandEntries) {\n const entry = manifest[island.src];\n if (entry?.file) {\n islandMap[island.key] = `/${entry.file}`;\n }\n }\n if (Object.keys(islandMap).length > 0) {\n islandRegistryScript = `<script>window.__L5E_ISLANDS__=${serialize(islandMap)}</script>`;\n }\n }\n\n if (cssSrcList.length > 0) {\n extraHead += cssSrcList\n .map((file) => `<link rel=\"stylesheet\" crossorigin href=\"${file}\">`)\n .join('');\n }\n }\n\n let cssHtml = '';\n if (!isProduction) {\n cssHtml = cssSrcList.map((src) => `<link rel=\"stylesheet\" href=\"${src}\">`).join('');\n }\n\n let allScripts = [...globalScripts, ...scriptSrcList];\n\n if (!isProduction) {\n const globalTsPath = path.join(root, 'src', 'client.global.ts');\n if (existsSync(globalTsPath)) {\n allScripts = ['/src/client.global.ts', ...allScripts];\n }\n\n if (islandEntries.length > 0) {\n const islandMap: Record<string, string> = {};\n for (const island of islandEntries) {\n islandMap[island.key] = `/${island.src}`;\n }\n islandRegistryScript = `<script>window.__L5E_ISLANDS__=${serialize(islandMap)}</script>`;\n }\n }\n\n const scriptsHtml =\n islandRegistryScript +\n allScripts.map((src) => `<script type=\"module\" src=\"${src}\"></script>`).join('');\n\n const templateWithLang = rendered.lang ? applyHtmlLang(template, rendered.lang) : template;\n\n // Replacer fns (not strings) so `$`-sequences in rendered HTML — e.g. an SSR\n // island body or user text containing \"$$\" / \"$&\" — aren't interpreted as\n // special String.replace replacement patterns.\n const html = rendered.rawHtml\n ? rendered.html || ''\n : templateWithLang\n .replace(`<!--app-head-->`, () => (rendered.head ?? '') + extraHead + cssHtml)\n .replace(`<!--app-html-->`, () => rendered.html ?? '')\n .replace(`<!--app-scripts-->`, () => scriptsHtml);\n\n const headers = new Headers({\n 'Content-Type': 'text/html',\n });\n\n const cacheControlParts: string[] = ['public'];\n if (maxAge !== undefined) cacheControlParts.push(`max-age=${maxAge}`);\n if (sMaxAge !== undefined) cacheControlParts.push(`s-maxage=${sMaxAge}`);\n if (swr !== undefined) cacheControlParts.push(`stale-while-revalidate=${swr}`);\n\n if (cacheControlParts.length > 1 && isProduction) {\n headers.set('Cache-Control', cacheControlParts.join(', '));\n }\n\n if (process.env.NODE_ENV === 'production') {\n cacheTags = optimizeCacheTags(cacheTags);\n }\n headers.set('Cache-Tag', ['global', ...cacheTags].join(','));\n\n return new globalThis.Response(html, {\n status: rendered.statusCode || 200,\n headers,\n });\n}\n\nexport async function createServer(options: ServerOptions = {}): Promise<ServerContext> {\n const root = options.root || process.cwd();\n const base = options.base || '/';\n const isProduction = process.env.NODE_ENV === 'production';\n\n // Cached production assets\n const templateHtml = isProduction\n ? await fs.readFile(path.join(root, './index.html'), 'utf-8')\n : '';\n\n // Create http server\n // @ts-ignore\n const express = (await import('express')).default;\n const app = options.app || express();\n\n // Serve static files from public directory\n if (options.publicDir) {\n const publicPath = path.isAbsolute(options.publicDir)\n ? options.publicDir\n : path.join(root, options.publicDir);\n\n if (existsSync(publicPath)) {\n app.use(express.static(publicPath));\n }\n }\n\n // Add Vite or respective production middlewares\n let vite: ViteDevServer | undefined;\n const distClientDir = path.join(root, './dist/client');\n\n if (!isProduction) {\n const { createServer } = await import('vite');\n const configFile = path.join(root, 'vite.config.js');\n vite = await createServer({\n root,\n configFile,\n server: { middlewareMode: true },\n appType: 'custom',\n base,\n optimizeDeps: {\n exclude: ['@withl5e/l5e', 'file-type'],\n },\n ssr: {\n resolve: {\n conditions: ['development', 'default'],\n },\n },\n resolve: {\n conditions: ['development', 'default'],\n },\n });\n app.use(vite.middlewares);\n } else {\n // @ts-ignore\n const compression = (await import('compression')).default;\n // @ts-ignore\n const sirv = (await import('sirv')).default;\n app.use(compression());\n app.use(base, sirv(distClientDir, { extensions: [] }));\n\n // Route để serve bundled files từ memory map\n // Äặt route nà y trước route HTML để catch request trước\n app.get(\n `${base === '/' ? '' : base}/bundle-:hash.:ext`,\n async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const { hash, ext } = req.params;\n const filename = `bundle-${hash}.${ext}`;\n const bundledFile = getBundledFile(filename);\n\n if (!bundledFile) {\n return res.status(404).send('Bundled file not found');\n }\n\n res.set({\n 'Content-Type': bundledFile.mimeType,\n 'Cache-Control': 'public, max-age=31536000, immutable',\n });\n res.send(bundledFile.content);\n } catch (e: any) {\n console.error('[server] Error serving bundled file:', e);\n res.status(500).end('Internal server error');\n }\n },\n );\n }\n\n // Action routes — JSON body parsing scoped to action endpoints only\n app.use('/_l5e/action', express.json({ limit: '100kb' }));\n\n // Validate action key format: actionName_hexHash\n const ACTION_KEY_RE = /^[a-zA-Z]\\w+_[0-9a-f]{1,4}$/;\n\n // Load action registry and viewActions glob from virtual module (dev) or built bundle (prod)\n let prodActionRegistry: Record<string, { modulePath: string; actionName: string }> | null = null;\n let prodViewActions: Record<string, () => Promise<any>> | null = null;\n\n async function getActionRegistry(): Promise<\n Record<string, { modulePath: string; actionName: string }>\n > {\n if (!isProduction) {\n const mod = await vite!.ssrLoadModule('virtual:l5e-actions');\n return mod.actionRegistry || {};\n }\n if (!prodActionRegistry) {\n const registryPath = path.join(root, './dist/server/action-registry.json');\n const json = await fs.readFile(registryPath, 'utf-8');\n prodActionRegistry = JSON.parse(json);\n }\n return prodActionRegistry!;\n }\n\n async function getViewActions(): Promise<Record<string, () => Promise<any>>> {\n if (!isProduction) {\n const mod = await vite!.ssrLoadModule('virtual:l5e-actions');\n return mod.viewActions || {};\n }\n if (!prodViewActions) {\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const mod = await import(pathToFileURL(entryServerPath).href);\n prodViewActions = mod.viewActions || {};\n }\n return prodViewActions!;\n }\n\n // Action route handler — hashed action keys\n // URL: /_l5e/action/:actionKey (e.g., /_l5e/action/loadMoreComments_a1b2)\n app.all('/_l5e/action/:actionKey', async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const { actionKey } = req.params;\n\n // Validate action key format\n if (!ACTION_KEY_RE.test(actionKey)) {\n return res.status(400).send('Invalid action key');\n }\n\n // Look up action in registry\n const registry = await getActionRegistry();\n const entry = registry[actionKey];\n if (!entry) {\n return res.status(404).send('Action not found');\n }\n\n const { modulePath, actionName } = entry;\n\n // Import action module via viewActions glob (works in both dev and prod)\n let actionModule: any;\n if (!isProduction) {\n try {\n actionModule = await vite!.ssrLoadModule(`/src/${modulePath}/actions.tsx`);\n } catch {\n actionModule = await vite!.ssrLoadModule(`/src/${modulePath}/actions.ts`);\n }\n } else {\n const viewActions = await getViewActions();\n // Find matching glob entry by modulePath\n const globKey = viewActions[`/src/${modulePath}/actions.tsx`]\n ? `/src/${modulePath}/actions.tsx`\n : viewActions[`/src/${modulePath}/actions.ts`]\n ? `/src/${modulePath}/actions.ts`\n : null;\n if (!globKey) {\n return res.status(404).send('Action module not found');\n }\n actionModule = await viewActions[globKey]();\n }\n\n // Look up exported action — use hasOwnProperty to avoid prototype pollution\n if (!Object.prototype.hasOwnProperty.call(actionModule, actionName)) {\n return res.status(404).send('Action not found');\n }\n const action = actionModule[actionName];\n if (!action || !action.handler) {\n return res.status(404).send('Action not found');\n }\n\n // Enforce the action's declared HTTP method. Previously `app.all` accepted\n // any method and `action.method` was ignored, so a state-changing POST\n // action could be triggered via GET (e.g. <img src>) — a CSRF vector.\n const allowedMethod = (action.method || 'GET').toUpperCase();\n if (req.method.toUpperCase() !== allowedMethod) {\n return res.status(405).set('Allow', allowedMethod).send('Method Not Allowed');\n }\n\n // Run the request through the middleware chain first — same as the HTML\n // handler — so `locals` (locale, country, preview flags, ...) is\n // populated for actions too. Previously this endpoint built its own\n // bare `requestInfo` and never invoked middleware at all, so `locals`\n // (and thus `getLocale()`) was always empty inside an action handler.\n //\n // Crucially, the action must run *inside* the `next` callback passed to\n // the middleware — not after `await`ing the middleware call — because a\n // middleware like `fromFetchMiddleware(paraglideMiddleware)` calls\n // `next()` synchronously inside a library-owned `AsyncLocalStorage.run()`\n // scope. Running the action afterward, once that scope has already\n // closed, would silently lose the library's own ambient `getLocale()`.\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const entryServerModule: EntryServerModule = isProduction\n ? await import(pathToFileURL(entryServerPath).href)\n : await vite!.ssrLoadModule('@withl5e/l5e/entry-server');\n const { runInRenderContext } = await (isProduction\n ? import(pathToFileURL(entryServerPath).href)\n : vite!.ssrLoadModule('@withl5e/l5e/jsx-runtime'));\n const { renderJsxToHtmlString } = await (isProduction\n ? import(pathToFileURL(entryServerPath).href)\n : vite!.ssrLoadModule('@withl5e/l5e'));\n\n const locals: Record<string, unknown> = {};\n const initialRequest = createWebRequestFromExpress(req);\n const middlewareContext = createContext({\n request: initialRequest,\n locals,\n clientAddress: requestIp.getClientIp(req) ?? undefined,\n });\n\n const loadedMiddleware = await entryServerModule.loadMiddleware?.();\n const middlewareHandler: MiddlewareHandler =\n typeof loadedMiddleware === 'function' ? loadedMiddleware : (_ctx, dummyNext) => dummyNext();\n\n // If middleware short-circuits (returns its own Response — a redirect, a\n // 403, ...) instead of calling `next`, this callback never runs and that\n // response wins, same as it would for a page request.\n const response = await middlewareHandler(middlewareContext, async (payload) => {\n const nextRequest = createRewriteRequest(payload, middlewareContext.request, middlewareContext.url);\n const requestInfo = {\n ...createRequestInfo(req, nextRequest, base, locals),\n path: req.originalUrl,\n body: req.body,\n };\n\n const html = await runInRenderContext(\n async () => {\n const jsx = await action.handler(requestInfo);\n return renderJsxToHtmlString(jsx);\n },\n requestInfo,\n modulePath,\n );\n\n return new Response(html, { headers: { 'Content-Type': 'text/html' } });\n });\n\n await sendWebResponse(req, res, response);\n } catch (e: any) {\n vite?.ssrFixStacktrace?.(e);\n console.error('[l5e] Action error:', e.stack || e);\n res.status(500).send('Internal server error');\n }\n });\n\n // Serve HTML\n app.use(async (req: ExpressRequest, res: ExpressResponse) => {\n try {\n const url = req.originalUrl.replace(base, '');\n\n let template: string;\n let render: (url: string, requestInfo?: any) => Promise<any>;\n let loadMiddleware: EntryServerModule['loadMiddleware'];\n let manifest: Record<string, any> | undefined;\n\n if (!isProduction) {\n // Always read fresh template in development\n template = await fs.readFile(path.join(root, './index.html'), 'utf-8');\n template = await vite!.transformIndexHtml(url, template);\n\n // Inject Vite HMR client for hot reload\n if (!template.includes('@vite/client')) {\n template = template.replace(\n '</head>',\n '<script type=\"module\" src=\"/@vite/client\"></script></head>',\n );\n }\n\n const entryServer = (await vite!.ssrLoadModule(\n '@withl5e/l5e/entry-server',\n )) as EntryServerModule;\n render = entryServer.render;\n loadMiddleware = entryServer.loadMiddleware;\n } else {\n template = templateHtml;\n const entryServerPath = path.join(root, './dist/server/entry-server.js');\n const entryServer = (await import(\n pathToFileURL(entryServerPath).href\n )) as EntryServerModule;\n render = entryServer.render;\n loadMiddleware = entryServer.loadMiddleware;\n // Read manifest to map hashed assets\n const manifestJson = await fs.readFile(\n path.join(root, './dist/client/.vite/manifest.json'),\n 'utf-8',\n );\n manifest = JSON.parse(manifestJson);\n }\n\n const loadedMiddleware = await loadMiddleware?.();\n const handler: MiddlewareHandler =\n typeof loadedMiddleware === 'function' ? loadedMiddleware : (_ctx, next) => next();\n\n const locals: Record<string, unknown> = {};\n const initialRequest = createWebRequestFromExpress(req);\n const context = createContext({\n request: initialRequest,\n requestInfo: createRequestInfo(req, initialRequest, base, locals),\n locals,\n clientAddress: requestIp.getClientIp(req),\n });\n\n const renderResponse = async (webRequest: globalThis.Request) => {\n const nextRequestInfo = createRequestInfo(req, webRequest, base, locals);\n const nextUrl = getRenderUrl(nextRequestInfo.url!, base);\n const nextRendered = await render(nextUrl, nextRequestInfo);\n return createPageResponse({\n rendered: nextRendered,\n template,\n manifest,\n root,\n distClientDir,\n isProduction,\n });\n };\n\n const next = async (payload?: RewritePayload) => {\n const nextRequest = createRewriteRequest(payload, context.request, context.url);\n context.request = nextRequest;\n context.url = new URL(nextRequest.url);\n context.cookies = parseCookies(nextRequest.headers.get('cookie') ?? undefined);\n context.requestInfo = createRequestInfo(req, nextRequest, base, locals);\n return renderResponse(nextRequest);\n };\n\n context.rewrite = (payload: RewritePayload) => next(payload);\n\n const response = await handler(context, next);\n await sendWebResponse(req, res, response);\n } catch (e: any) {\n vite?.ssrFixStacktrace?.(e);\n console.error(e.stack);\n // Never leak stack traces to the client in production (info disclosure).\n res.status(500).end(isProduction ? 'Internal Server Error' : e.stack);\n }\n });\n\n return { app, vite };\n}\n\nexport async function startServer(options: ServerOptions = {}): Promise<void> {\n const port = options.port || 5173;\n\n // Create Express app first\n // @ts-ignore\n const express = (await import('express')).default;\n const app = express();\n\n // Call callback if provided to allow custom routes before setting up L5E server\n if (options.setupApp) {\n console.log('setupApp');\n await options.setupApp(app);\n }\n\n const { app: serverApp } = await createServer({ ...options, app });\n\n serverApp.listen(port, () => {\n console.log(`Server started at http://localhost:${port}`);\n });\n}\n\nconst MAX_TAGS = 1000;\n\nexport function hashTag(tag: string): string {\n // global tag is not hashed, better for ci/cd\n if (tag === 'global') {\n return 'global';\n }\n\n let hash = 0;\n for (let i = 0; i < tag.length; i++) {\n const char = tag.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32bit integer\n }\n return Math.abs(hash).toString(36).substring(0, 8);\n}\n\nexport function optimizeCacheTags(tags: Set<string> | string[]): string[] {\n const _tags = Array.isArray(tags) ? tags : [...tags];\n const result = _tags.slice(0, MAX_TAGS).map(hashTag);\n return result;\n}\n"],"names":["rollupModulePromise","loadRollup","require","createRequire","vitePath","rollupPath","path","pathToFileURL","bundledFilesMap","bundleCache","cssCache","generateHash","content","createHash","bundleScripts","scriptPaths","rootDir","distClientDir","uniquePaths","cacheKey","cachedEntryFileName","entryFile","hash","tempDir","fs","entryContent","p","i","filePath","rollupOptions","source","importer","_options","resolved","id","outputOptions","rollup","bundle","output","bundledFile","entryChunk","error","bundleCss","cssPaths","cachedFileName","cachedFile","cssContents","cssPath","err","bundledContent","filename","getBundledFile","applyHtmlLang","template","lang","safeLang","escapeProp","match","attrs","getRequestUrl","req","getRenderUrl","urlObject","base","createWebRequestFromExpress","init","createHeadersFromExpressRequest","createRequestInfo","webRequest","locals","renderUrl","normalizedPath","headers","value","key","parseCookies","requestIp","createRewriteRequest","payload","currentRequest","currentUrl","sendWebResponse","res","response","setCookieValues","getSetCookieHeaders","body","getSetCookie","raw","splitSetCookieHeader","cookies","start","rest","createRawResponse","rendered","contentType","statusCode","responseHeaders","createPageResponse","manifest","root","isProduction","rawResponse","scriptSrcList","cssSrcList","islandEntries","cacheTags","maxAge","sMaxAge","swr","extraHead","globalScripts","islandRegistryScript","collectFromEntry","entryKey","entry","css","cssFiles","importKey","importedChunk","preloadFiles","chunk","src","mappedScripts","file","mappedCssFiles","bundledScript","bundledCss","globalEntry","cssFile","islandMap","island","serialize","cssHtml","allScripts","globalTsPath","existsSync","scriptsHtml","templateWithLang","html","cacheControlParts","optimizeCacheTags","createServer","options","templateHtml","express","app","publicPath","vite","compression","sirv","ext","e","configFile","ACTION_KEY_RE","prodActionRegistry","prodViewActions","getActionRegistry","registryPath","json","getViewActions","entryServerPath","actionKey","modulePath","actionName","actionModule","viewActions","globKey","action","allowedMethod","entryServerModule","runInRenderContext","renderJsxToHtmlString","initialRequest","middlewareContext","createContext","loadedMiddleware","_ctx","dummyNext","nextRequest","requestInfo","jsx","url","render","loadMiddleware","entryServer","manifestJson","handler","next","context","renderResponse","nextRequestInfo","nextUrl","nextRendered","startServer","port","serverApp","MAX_TAGS","hashTag","tag","char","tags"],"mappings":"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"}
|
package/dist/tooltip.js
CHANGED
|
@@ -1,129 +1,147 @@
|
|
|
1
|
-
import { autoUpdate as u, computePosition as
|
|
2
|
-
function
|
|
3
|
-
const
|
|
4
|
-
return e ||
|
|
1
|
+
import { autoUpdate as u, computePosition as m, offset as f, flip as h, shift as v, hide as b } from "@floating-ui/dom";
|
|
2
|
+
function y() {
|
|
3
|
+
const t = navigator.userAgent.toLowerCase(), e = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile|tablet/i.test(t), o = window.matchMedia("(max-width: 768px)").matches;
|
|
4
|
+
return e || o;
|
|
5
5
|
}
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
const e =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const { showTooltipMobile: i } = await Promise.resolve().then(() =>
|
|
14
|
-
i(
|
|
15
|
-
}) :
|
|
6
|
+
function w() {
|
|
7
|
+
const t = document.querySelectorAll("[data-tooltip-id]");
|
|
8
|
+
if (t.length === 0) return;
|
|
9
|
+
const e = y();
|
|
10
|
+
t.forEach((o) => {
|
|
11
|
+
o.hasAttribute("data-tooltip-initialized") || (o.setAttribute("data-tooltip-initialized", "true"), e ? o.addEventListener("click", async (n) => {
|
|
12
|
+
n.preventDefault();
|
|
13
|
+
const { showTooltipMobile: i } = await Promise.resolve().then(() => d);
|
|
14
|
+
i(o);
|
|
15
|
+
}) : o.addEventListener(
|
|
16
16
|
"pointerenter",
|
|
17
17
|
async () => {
|
|
18
|
-
const { showTooltip:
|
|
19
|
-
o
|
|
18
|
+
const { showTooltip: n } = await Promise.resolve().then(() => d);
|
|
19
|
+
n(o);
|
|
20
20
|
},
|
|
21
21
|
{ passive: !0 }
|
|
22
22
|
));
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
-
function
|
|
25
|
+
function _() {
|
|
26
26
|
if (window.__tooltipObserver) return;
|
|
27
|
-
const
|
|
28
|
-
let
|
|
29
|
-
e.forEach((
|
|
30
|
-
|
|
27
|
+
const t = new MutationObserver((e) => {
|
|
28
|
+
let o = !1;
|
|
29
|
+
e.forEach((n) => {
|
|
30
|
+
n.type === "childList" && n.addedNodes.length > 0 && n.addedNodes.forEach((i) => {
|
|
31
31
|
if (i.nodeType === Node.ELEMENT_NODE) {
|
|
32
|
-
const
|
|
33
|
-
(
|
|
32
|
+
const a = i;
|
|
33
|
+
(a.hasAttribute("data-tooltip-id") || a.querySelector("[data-tooltip-id]")) && (o = !0);
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
-
}),
|
|
36
|
+
}), o && w();
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
t.observe(document.body, {
|
|
39
39
|
childList: !0,
|
|
40
40
|
subtree: !0
|
|
41
|
-
}), window.__tooltipObserver =
|
|
41
|
+
}), window.__tooltipObserver = t;
|
|
42
|
+
}
|
|
43
|
+
const g = ({ type: t, id: e }) => `/tooltip/${t}/${e}`, L = ({ type: t, id: e }) => {
|
|
44
|
+
const o = document.documentElement.lang;
|
|
45
|
+
let n = "";
|
|
46
|
+
if (o) {
|
|
47
|
+
const i = o.split("-")[0].toLowerCase(), l = location.pathname.match(/^\/([a-zA-Z-]+)(?:\/|$)/)?.[1];
|
|
48
|
+
l && (l.toLowerCase() === i || l.toLowerCase() === o.toLowerCase()) && (n = `/${l}`);
|
|
49
|
+
}
|
|
50
|
+
return `${n}/tooltip/${t}/${e}`;
|
|
51
|
+
};
|
|
52
|
+
function T(t) {
|
|
53
|
+
window.__l5eTooltipUrl = t === "auto-locale" ? L : t;
|
|
42
54
|
}
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
function p(t) {
|
|
56
|
+
const { tooltipId: e, tooltipType: o } = t.dataset;
|
|
57
|
+
return (window.__l5eTooltipUrl ?? g)({ type: o ?? "", id: e ?? "", host: t });
|
|
58
|
+
}
|
|
59
|
+
async function E(t) {
|
|
60
|
+
if (!t.matches(":hover")) return;
|
|
45
61
|
const e = document.createElement("div");
|
|
46
62
|
e.className = "tp", e.innerHTML = '<div class="tp-loading">Loading...</div>', document.body.append(e);
|
|
47
|
-
let
|
|
48
|
-
const
|
|
49
|
-
|
|
63
|
+
let o = null;
|
|
64
|
+
const n = () => {
|
|
65
|
+
o && (o(), o = null), document.body.contains(e) && e.remove();
|
|
50
66
|
};
|
|
51
|
-
if (
|
|
52
|
-
|
|
67
|
+
if (t.addEventListener("pointerleave", n), !t.matches(":hover")) {
|
|
68
|
+
t.removeEventListener("pointerleave", n), e.remove();
|
|
53
69
|
return;
|
|
54
70
|
}
|
|
55
|
-
|
|
71
|
+
o = u(t, e, () => s(t, e));
|
|
56
72
|
try {
|
|
57
|
-
const
|
|
73
|
+
const i = await fetch(p(t), {
|
|
58
74
|
headers: { Accept: "text/html" }
|
|
59
75
|
}).then((a) => a.text());
|
|
60
76
|
if (!document.body.contains(e)) return;
|
|
61
|
-
e.innerHTML =
|
|
77
|
+
e.innerHTML = i, await s(t, e);
|
|
62
78
|
} catch {
|
|
63
79
|
document.body.contains(e) && (e.innerHTML = '<div class="tp-error">Không thể tải tooltip</div>');
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
|
-
async function
|
|
82
|
+
async function x(t) {
|
|
67
83
|
const e = document.createElement("div");
|
|
68
84
|
e.className = "tp-overlay";
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
85
|
+
const o = document.createElement("div");
|
|
86
|
+
o.className = "tp-mobile", o.innerHTML = '<div class="tp-loading">Loading...</div>', e.append(o), document.body.append(e), document.body.style.overflow = "hidden";
|
|
87
|
+
const n = () => {
|
|
72
88
|
e.remove(), document.body.style.overflow = "";
|
|
73
89
|
};
|
|
74
90
|
e.addEventListener("click", (i) => {
|
|
75
|
-
i.target === e &&
|
|
91
|
+
i.target === e && n();
|
|
76
92
|
});
|
|
77
93
|
try {
|
|
78
|
-
const
|
|
94
|
+
const i = await fetch(p(t), {
|
|
79
95
|
headers: { Accept: "text/html" }
|
|
80
|
-
}).then((
|
|
81
|
-
|
|
96
|
+
}).then((c) => c.text());
|
|
97
|
+
o.innerHTML = "";
|
|
82
98
|
const a = document.createElement("button");
|
|
83
|
-
a.className = "tp-mobile-close", a.innerHTML = "✕", a.addEventListener("click",
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
const
|
|
89
|
-
|
|
99
|
+
a.className = "tp-mobile-close", a.innerHTML = "✕", a.addEventListener("click", n), o.append(a);
|
|
100
|
+
const l = document.createElement("div");
|
|
101
|
+
l.className = "tp-mobile-content", l.innerHTML = i, o.append(l);
|
|
102
|
+
const r = t.dataset.href;
|
|
103
|
+
if (r) {
|
|
104
|
+
const c = document.createElement("a");
|
|
105
|
+
c.className = "tp-mobile-link", c.href = r, c.textContent = "Xem chi tiết", o.append(c);
|
|
90
106
|
}
|
|
91
107
|
} catch {
|
|
92
|
-
|
|
108
|
+
o.innerHTML = '<div class="tp-error">Không thể tải tooltip</div>';
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
|
-
function
|
|
111
|
+
function M() {
|
|
96
112
|
return {
|
|
97
113
|
name: "centerFallback",
|
|
98
|
-
fn({ rects:
|
|
99
|
-
const e = window.innerHeight,
|
|
100
|
-
return
|
|
101
|
-
y: window.scrollY + Math.max(
|
|
102
|
-
data: { centered: !0, maxHeight: e -
|
|
114
|
+
fn({ rects: t }) {
|
|
115
|
+
const e = window.innerHeight, o = 8;
|
|
116
|
+
return t.floating.height <= e * 0.6 ? {} : {
|
|
117
|
+
y: window.scrollY + Math.max(o, (e - t.floating.height) / 2),
|
|
118
|
+
data: { centered: !0, maxHeight: e - o * 2 }
|
|
103
119
|
};
|
|
104
120
|
}
|
|
105
121
|
};
|
|
106
122
|
}
|
|
107
|
-
function
|
|
108
|
-
const
|
|
109
|
-
return
|
|
110
|
-
placement:
|
|
111
|
-
middleware: [
|
|
112
|
-
}).then(({ x: i, y:
|
|
113
|
-
Object.assign(e.style, { left: `${i}px`, top: `${
|
|
114
|
-
const
|
|
115
|
-
|
|
123
|
+
function s(t, e) {
|
|
124
|
+
const n = t.dataset.tooltipPlacement ?? "left";
|
|
125
|
+
return m(t, e, {
|
|
126
|
+
placement: n,
|
|
127
|
+
middleware: [f(6), h(), v({ padding: 5 }), M(), b()]
|
|
128
|
+
}).then(({ x: i, y: a, middlewareData: l }) => {
|
|
129
|
+
Object.assign(e.style, { left: `${i}px`, top: `${a}px` }), e.style.visibility = l.hide?.referenceHidden ? "hidden" : "visible";
|
|
130
|
+
const r = l.centerFallback;
|
|
131
|
+
r?.centered ? (e.style.maxHeight = `${r.maxHeight}px`, e.style.overflowY = "auto") : (e.style.maxHeight = "", e.style.overflowY = "");
|
|
116
132
|
});
|
|
117
133
|
}
|
|
118
|
-
const
|
|
134
|
+
const d = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
119
135
|
__proto__: null,
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
configureTooltip: T,
|
|
137
|
+
showTooltip: E,
|
|
138
|
+
showTooltipMobile: x
|
|
122
139
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
123
140
|
export {
|
|
124
|
-
T as
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
E as
|
|
141
|
+
T as configureTooltip,
|
|
142
|
+
w as initTooltips,
|
|
143
|
+
_ as setupTooltipObserver,
|
|
144
|
+
E as showTooltip,
|
|
145
|
+
x as showTooltipMobile
|
|
128
146
|
};
|
|
129
147
|
//# sourceMappingURL=tooltip.js.map
|
package/dist/tooltip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","sources":["../src/tooltip/tooltip-loader.ts","../src/tooltip/tooltip-runtime.ts"],"sourcesContent":["// tooltip-loader.ts\n// File này chỉ làm nhiệm vụ wake up tooltip khi cần thiết\n\n// Định nghĩa kiểu cho tooltip host element\ntype TooltipHost = HTMLElement & {\n dataset: {\n tooltipId: string;\n tooltipType?: string;\n };\n};\n\n/**\n * Kiểm tra xem thiết bị hiện tại có phải là thiết bị di động hay không\n * @returns true nếu là thiết bị di động\n */\nfunction isMobileDevice(): boolean {\n // Kiểm tra User Agent\n const userAgent = navigator.userAgent.toLowerCase();\n const isMobile =\n /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile|tablet/i.test(userAgent);\n\n // Kiểm tra thêm kích thước màn hình (dưới 768px thường được coi là thiết bị di động)\n const isTouchScreen = window.matchMedia('(max-width: 768px)').matches;\n\n return isMobile || isTouchScreen;\n}\n\n/**\n * Hàm khởi tạo tooltip, chỉ load runtime khi cần thiết\n */\nexport function initTooltips(): void {\n const tooltipTriggers = document.querySelectorAll<TooltipHost>('[data-tooltip-id]');\n if (tooltipTriggers.length === 0) return;\n\n const mobile = isMobileDevice();\n\n tooltipTriggers.forEach((trigger) => {\n if (trigger.hasAttribute('data-tooltip-initialized')) return;\n trigger.setAttribute('data-tooltip-initialized', 'true');\n\n if (mobile) {\n trigger.addEventListener('click', async (e) => {\n e.preventDefault();\n const { showTooltipMobile } = await import('./tooltip-runtime');\n showTooltipMobile(trigger);\n });\n } else {\n trigger.addEventListener(\n 'pointerenter',\n async () => {\n const { showTooltip } = await import('./tooltip-runtime');\n showTooltip(trigger);\n },\n { passive: true },\n );\n }\n });\n}\n\n// Thêm một MutationObserver để theo dõi các phần tử DOM mới (cho React components)\nexport function setupTooltipObserver(): void {\n // Kiểm tra xem đã có observer chưa\n if (window.__tooltipObserver) return;\n\n // Observer theo dõi khi có phần tử mới được thêm vào DOM\n const observer = new MutationObserver((mutations) => {\n let shouldInit = false;\n\n mutations.forEach((mutation) => {\n if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {\n // Kiểm tra xem có phần tử tooltip nào mới được thêm vào không\n mutation.addedNodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Kiểm tra nếu phần tử có data-tooltip-id hoặc chứa phần tử con có data-tooltip-id\n if (\n element.hasAttribute('data-tooltip-id') ||\n element.querySelector('[data-tooltip-id]')\n ) {\n shouldInit = true;\n }\n }\n });\n }\n });\n\n // Chỉ gọi initTooltips nếu có phát hiện tooltip mới\n if (shouldInit) {\n initTooltips();\n }\n });\n\n // Bắt đầu theo dõi toàn bộ DOM\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n // Lưu observer vào window để tránh tạo nhiều observer\n window.__tooltipObserver = observer;\n}\n\n// Typescript declaration cho window object\ndeclare global {\n interface Window {\n __tooltipObserver?: MutationObserver;\n }\n}\n","// tooltip-runtime.ts\n// Logic đầy đủ cho tooltip, chỉ được tải khi cần thiết\n\nimport type { Middleware, Placement } from '@floating-ui/dom';\nimport { autoUpdate, computePosition, flip, hide, offset, shift } from '@floating-ui/dom';\n\ntype TooltipHost = HTMLElement & {\n dataset: {\n tooltipId: string;\n tooltipType?: string;\n tooltipPlacement?: Placement;\n href?: string;\n };\n};\n\nexport async function showTooltip(host: TooltipHost): Promise<void> {\n /*\n * Trong lúc chờ dynamic import, con trỏ có thể đã rời khỏi phần tử host.\n * Nếu vậy, việc tạo tooltip là không cần thiết và sẽ không có sự kiện\n * `pointerleave` nào được kích hoạt để ẩn tooltip.\n */\n if (!host.matches(':hover')) return;\n\n const tip = document.createElement('div');\n tip.className = 'tp';\n tip.innerHTML = '<div class=\"tp-loading\">Loading...</div>';\n document.body.append(tip);\n\n // --- Cleanup ---\n let cleanupAutoUpdate: (() => void) | null = null;\n\n const cleanup = () => {\n if (cleanupAutoUpdate) {\n cleanupAutoUpdate();\n cleanupAutoUpdate = null;\n }\n if (document.body.contains(tip)) tip.remove();\n };\n\n host.addEventListener('pointerleave', cleanup);\n\n // Con trỏ đã rời trước khi listener được gắn\n if (!host.matches(':hover')) {\n host.removeEventListener('pointerleave', cleanup);\n tip.remove();\n return;\n }\n\n // --- Position: autoUpdate thay thế manual scroll/resize ---\n cleanupAutoUpdate = autoUpdate(host, tip, () => position(host, tip));\n\n // --- Fetch nội dung từ server ---\n try {\n const { tooltipId: id, tooltipType: type } = host.dataset;\n const html = await fetch(`/tooltip/${type}/${id}`, {\n headers: { Accept: 'text/html' },\n }).then((r) => r.text());\n\n if (!document.body.contains(tip)) return;\n\n tip.innerHTML = html;\n await position(host, tip);\n } catch {\n if (document.body.contains(tip)) {\n tip.innerHTML = '<div class=\"tp-error\">Không thể tải tooltip</div>';\n }\n }\n}\n\n/**\n * Mobile: hiển thị tooltip dạng fullscreen popup.\n * Nếu host có data-href, thêm nút \"Xem chi tiết\" dẫn đến link đó.\n */\nexport async function showTooltipMobile(host: TooltipHost): Promise<void> {\n const overlay = document.createElement('div');\n overlay.className = 'tp-overlay';\n\n const popup = document.createElement('div');\n popup.className = 'tp-mobile';\n popup.innerHTML = '<div class=\"tp-loading\">Loading...</div>';\n\n overlay.append(popup);\n document.body.append(overlay);\n document.body.style.overflow = 'hidden';\n\n const cleanup = () => {\n overlay.remove();\n document.body.style.overflow = '';\n };\n\n // Đóng khi bấm vào overlay (bên ngoài popup)\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) cleanup();\n });\n\n try {\n const { tooltipId: id, tooltipType: type } = host.dataset;\n const html = await fetch(`/tooltip/${type}/${id}`, {\n headers: { Accept: 'text/html' },\n }).then((r) => r.text());\n\n popup.innerHTML = '';\n\n // Nút đóng\n const closeBtn = document.createElement('button');\n closeBtn.className = 'tp-mobile-close';\n closeBtn.innerHTML = '✕';\n closeBtn.addEventListener('click', cleanup);\n popup.append(closeBtn);\n\n // Nội dung\n const content = document.createElement('div');\n content.className = 'tp-mobile-content';\n content.innerHTML = html;\n popup.append(content);\n\n // Nút \"Xem chi tiết\" nếu có data-href\n const href = host.dataset.href;\n if (href) {\n const link = document.createElement('a');\n link.className = 'tp-mobile-link';\n link.href = href;\n link.textContent = 'Xem chi tiết';\n popup.append(link);\n }\n } catch {\n popup.innerHTML = '<div class=\"tp-error\">Không thể tải tooltip</div>';\n }\n}\n\n/**\n * Khi tooltip cao hơn 60% viewport, bỏ qua placement trên/dưới host\n * và căn giữa theo chiều dọc màn hình + giới hạn maxHeight để scroll.\n */\nfunction centerFallback(): Middleware {\n return {\n name: 'centerFallback',\n fn({ rects }) {\n const vh = window.innerHeight;\n const padding = 8;\n\n if (rects.floating.height <= vh * 0.6) return {};\n\n return {\n y: window.scrollY + Math.max(padding, (vh - rects.floating.height) / 2),\n data: { centered: true, maxHeight: vh - padding * 2 },\n };\n },\n };\n}\n\nfunction position(host: TooltipHost, tip: HTMLDivElement): Promise<void> {\n const preferredPlacement = host.dataset.tooltipPlacement;\n const placement = preferredPlacement ?? 'left';\n\n return computePosition(host, tip, {\n placement,\n middleware: [offset(6), flip(), shift({ padding: 5 }), centerFallback(), hide()],\n }).then(({ x, y, middlewareData }) => {\n Object.assign(tip.style, { left: `${x}px`, top: `${y}px` });\n\n tip.style.visibility = middlewareData.hide?.referenceHidden ? 'hidden' : 'visible';\n\n const center = middlewareData.centerFallback as { centered?: boolean; maxHeight?: number };\n if (center?.centered) {\n tip.style.maxHeight = `${center.maxHeight}px`;\n tip.style.overflowY = 'auto';\n } else {\n tip.style.maxHeight = '';\n tip.style.overflowY = '';\n }\n });\n}\n"],"names":["isMobileDevice","userAgent","isMobile","isTouchScreen","initTooltips","tooltipTriggers","mobile","trigger","e","showTooltipMobile","tooltipRuntime","showTooltip","setupTooltipObserver","observer","mutations","shouldInit","mutation","node","element","host","tip","cleanupAutoUpdate","cleanup","autoUpdate","position","id","type","html","r","overlay","popup","closeBtn","content","href","link","centerFallback","rects","vh","padding","placement","computePosition","offset","flip","shift","hide","x","y","middlewareData","center"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","sources":["../src/tooltip/tooltip-loader.ts","../src/tooltip/tooltip-runtime.ts"],"sourcesContent":["// tooltip-loader.ts\n// File này chỉ làm nhiệm vụ wake up tooltip khi cần thiết\n\n// Định nghĩa kiểu cho tooltip host element\ntype TooltipHost = HTMLElement & {\n dataset: {\n tooltipId: string;\n tooltipType?: string;\n };\n};\n\n/**\n * Kiểm tra xem thiết bị hiện tại có phải là thiết bị di động hay không\n * @returns true nếu là thiết bị di động\n */\nfunction isMobileDevice(): boolean {\n // Kiểm tra User Agent\n const userAgent = navigator.userAgent.toLowerCase();\n const isMobile =\n /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile|tablet/i.test(userAgent);\n\n // Kiểm tra thêm kích thước màn hình (dưới 768px thường được coi là thiết bị di động)\n const isTouchScreen = window.matchMedia('(max-width: 768px)').matches;\n\n return isMobile || isTouchScreen;\n}\n\n/**\n * Hàm khởi tạo tooltip, chỉ load runtime khi cần thiết\n */\nexport function initTooltips(): void {\n const tooltipTriggers = document.querySelectorAll<TooltipHost>('[data-tooltip-id]');\n if (tooltipTriggers.length === 0) return;\n\n const mobile = isMobileDevice();\n\n tooltipTriggers.forEach((trigger) => {\n if (trigger.hasAttribute('data-tooltip-initialized')) return;\n trigger.setAttribute('data-tooltip-initialized', 'true');\n\n if (mobile) {\n trigger.addEventListener('click', async (e) => {\n e.preventDefault();\n const { showTooltipMobile } = await import('./tooltip-runtime');\n showTooltipMobile(trigger);\n });\n } else {\n trigger.addEventListener(\n 'pointerenter',\n async () => {\n const { showTooltip } = await import('./tooltip-runtime');\n showTooltip(trigger);\n },\n { passive: true },\n );\n }\n });\n}\n\n// Thêm một MutationObserver để theo dõi các phần tử DOM mới (cho React components)\nexport function setupTooltipObserver(): void {\n // Kiểm tra xem đã có observer chưa\n if (window.__tooltipObserver) return;\n\n // Observer theo dõi khi có phần tử mới được thêm vào DOM\n const observer = new MutationObserver((mutations) => {\n let shouldInit = false;\n\n mutations.forEach((mutation) => {\n if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {\n // Kiểm tra xem có phần tử tooltip nào mới được thêm vào không\n mutation.addedNodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Kiểm tra nếu phần tử có data-tooltip-id hoặc chứa phần tử con có data-tooltip-id\n if (\n element.hasAttribute('data-tooltip-id') ||\n element.querySelector('[data-tooltip-id]')\n ) {\n shouldInit = true;\n }\n }\n });\n }\n });\n\n // Chỉ gọi initTooltips nếu có phát hiện tooltip mới\n if (shouldInit) {\n initTooltips();\n }\n });\n\n // Bắt đầu theo dõi toàn bộ DOM\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n // Lưu observer vào window để tránh tạo nhiều observer\n window.__tooltipObserver = observer;\n}\n\n// Typescript declaration cho window object\ndeclare global {\n interface Window {\n __tooltipObserver?: MutationObserver;\n }\n}\n","// tooltip-runtime.ts\n// Logic đầy đủ cho tooltip, chỉ được tải khi cần thiết\n\nimport type { Middleware, Placement } from '@floating-ui/dom';\nimport { autoUpdate, computePosition, flip, hide, offset, shift } from '@floating-ui/dom';\n\ntype TooltipHost = HTMLElement & {\n dataset: {\n tooltipId: string;\n tooltipType?: string;\n tooltipPlacement?: Placement;\n href?: string;\n };\n};\n\n/** Everything the tooltip system knows about one request, for a custom strategy to build a URL from. */\nexport interface TooltipUrlContext {\n type: string;\n id: string;\n host: TooltipHost;\n}\n\nexport type TooltipUrlStrategy = (ctx: TooltipUrlContext) => string;\n\nconst defaultStrategy: TooltipUrlStrategy = ({ type, id }) => `/tooltip/${type}/${id}`;\n\n/**\n * If the page's own URL already carries a prefix matching `<html lang>` (e.g.\n * `<html lang=\"vi\">` on `/vi/...`), reuse that same prefix — for apps doing\n * URL-prefix locale routing that want each locale to be a distinct,\n * CDN-cacheable tooltip URL with zero per-page setup. A base-locale page\n * (unprefixed URL) naturally infers no prefix.\n */\nconst autoLocaleStrategy: TooltipUrlStrategy = ({ type, id }) => {\n const lang = document.documentElement.lang;\n let prefix = '';\n if (lang) {\n const primary = lang.split('-')[0].toLowerCase();\n const match = location.pathname.match(/^\\/([a-zA-Z-]+)(?:\\/|$)/);\n const segment = match?.[1];\n if (segment && (segment.toLowerCase() === primary || segment.toLowerCase() === lang.toLowerCase())) {\n prefix = `/${segment}`;\n }\n }\n return `${prefix}/tooltip/${type}/${id}`;\n};\n\ndeclare global {\n interface Window {\n // L5E's per-request bundler compiles each view's client script (and\n // client.global.ts) as independent bundles — a view that imports\n // initTooltips()/showTooltip() gets its own inlined copy of this whole\n // module, with its own module-scoped variables. A plain `let` here\n // would only ever be seen by whichever single bundle happens to define\n // it, not by the others — so this has to live on `window`, the one\n // thing every independently-bundled script actually shares.\n __l5eTooltipUrl?: TooltipUrlStrategy;\n }\n}\n\n/**\n * Replace how every tooltip's fetch URL is built. L5E is not an i18n\n * framework and most apps using tooltips don't localize URLs (or need any\n * custom scheme) at all, so the default is `/tooltip/:type/:id` unless an\n * app opts into something else — call this once at startup, e.g. in\n * `client.global.ts` so it runs regardless of which view's own client\n * script actually mounts a tooltip:\n *\n * ```ts\n * configureTooltip('auto-locale'); // infer /vi (or none) from <html lang> + URL\n * // or fully custom:\n * configureTooltip(({ type, id }) => `/api/v2/tooltips/${type}-${id}`);\n * ```\n */\nexport function configureTooltip(strategy: 'auto-locale' | TooltipUrlStrategy): void {\n window.__l5eTooltipUrl = strategy === 'auto-locale' ? autoLocaleStrategy : strategy;\n}\n\nfunction tooltipUrl(host: TooltipHost): string {\n const { tooltipId: id, tooltipType: type } = host.dataset;\n const strategy = window.__l5eTooltipUrl ?? defaultStrategy;\n return strategy({ type: type ?? '', id: id ?? '', host });\n}\n\nexport async function showTooltip(host: TooltipHost): Promise<void> {\n /*\n * Trong lúc chờ dynamic import, con trỏ có thể đã rời khỏi phần tử host.\n * Nếu vậy, việc tạo tooltip là không cần thiết và sẽ không có sự kiện\n * `pointerleave` nào được kích hoạt để ẩn tooltip.\n */\n if (!host.matches(':hover')) return;\n\n const tip = document.createElement('div');\n tip.className = 'tp';\n tip.innerHTML = '<div class=\"tp-loading\">Loading...</div>';\n document.body.append(tip);\n\n // --- Cleanup ---\n let cleanupAutoUpdate: (() => void) | null = null;\n\n const cleanup = () => {\n if (cleanupAutoUpdate) {\n cleanupAutoUpdate();\n cleanupAutoUpdate = null;\n }\n if (document.body.contains(tip)) tip.remove();\n };\n\n host.addEventListener('pointerleave', cleanup);\n\n // Con trỏ đã rời trước khi listener được gắn\n if (!host.matches(':hover')) {\n host.removeEventListener('pointerleave', cleanup);\n tip.remove();\n return;\n }\n\n // --- Position: autoUpdate thay thế manual scroll/resize ---\n cleanupAutoUpdate = autoUpdate(host, tip, () => position(host, tip));\n\n // --- Fetch nội dung từ server ---\n try {\n const html = await fetch(tooltipUrl(host), {\n headers: { Accept: 'text/html' },\n }).then((r) => r.text());\n\n if (!document.body.contains(tip)) return;\n\n tip.innerHTML = html;\n await position(host, tip);\n } catch {\n if (document.body.contains(tip)) {\n tip.innerHTML = '<div class=\"tp-error\">Không thể tải tooltip</div>';\n }\n }\n}\n\n/**\n * Mobile: hiển thị tooltip dạng fullscreen popup.\n * Nếu host có data-href, thêm nút \"Xem chi tiết\" dẫn đến link đó.\n */\nexport async function showTooltipMobile(host: TooltipHost): Promise<void> {\n const overlay = document.createElement('div');\n overlay.className = 'tp-overlay';\n\n const popup = document.createElement('div');\n popup.className = 'tp-mobile';\n popup.innerHTML = '<div class=\"tp-loading\">Loading...</div>';\n\n overlay.append(popup);\n document.body.append(overlay);\n document.body.style.overflow = 'hidden';\n\n const cleanup = () => {\n overlay.remove();\n document.body.style.overflow = '';\n };\n\n // Đóng khi bấm vào overlay (bên ngoài popup)\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) cleanup();\n });\n\n try {\n const html = await fetch(tooltipUrl(host), {\n headers: { Accept: 'text/html' },\n }).then((r) => r.text());\n\n popup.innerHTML = '';\n\n // Nút đóng\n const closeBtn = document.createElement('button');\n closeBtn.className = 'tp-mobile-close';\n closeBtn.innerHTML = '✕';\n closeBtn.addEventListener('click', cleanup);\n popup.append(closeBtn);\n\n // Nội dung\n const content = document.createElement('div');\n content.className = 'tp-mobile-content';\n content.innerHTML = html;\n popup.append(content);\n\n // Nút \"Xem chi tiết\" nếu có data-href\n const href = host.dataset.href;\n if (href) {\n const link = document.createElement('a');\n link.className = 'tp-mobile-link';\n link.href = href;\n link.textContent = 'Xem chi tiết';\n popup.append(link);\n }\n } catch {\n popup.innerHTML = '<div class=\"tp-error\">Không thể tải tooltip</div>';\n }\n}\n\n/**\n * Khi tooltip cao hơn 60% viewport, bỏ qua placement trên/dưới host\n * và căn giữa theo chiều dọc màn hình + giới hạn maxHeight để scroll.\n */\nfunction centerFallback(): Middleware {\n return {\n name: 'centerFallback',\n fn({ rects }) {\n const vh = window.innerHeight;\n const padding = 8;\n\n if (rects.floating.height <= vh * 0.6) return {};\n\n return {\n y: window.scrollY + Math.max(padding, (vh - rects.floating.height) / 2),\n data: { centered: true, maxHeight: vh - padding * 2 },\n };\n },\n };\n}\n\nfunction position(host: TooltipHost, tip: HTMLDivElement): Promise<void> {\n const preferredPlacement = host.dataset.tooltipPlacement;\n const placement = preferredPlacement ?? 'left';\n\n return computePosition(host, tip, {\n placement,\n middleware: [offset(6), flip(), shift({ padding: 5 }), centerFallback(), hide()],\n }).then(({ x, y, middlewareData }) => {\n Object.assign(tip.style, { left: `${x}px`, top: `${y}px` });\n\n tip.style.visibility = middlewareData.hide?.referenceHidden ? 'hidden' : 'visible';\n\n const center = middlewareData.centerFallback as { centered?: boolean; maxHeight?: number };\n if (center?.centered) {\n tip.style.maxHeight = `${center.maxHeight}px`;\n tip.style.overflowY = 'auto';\n } else {\n tip.style.maxHeight = '';\n tip.style.overflowY = '';\n }\n });\n}\n"],"names":["isMobileDevice","userAgent","isMobile","isTouchScreen","initTooltips","tooltipTriggers","mobile","trigger","e","showTooltipMobile","tooltipRuntime","showTooltip","setupTooltipObserver","observer","mutations","shouldInit","mutation","node","element","defaultStrategy","type","id","autoLocaleStrategy","lang","prefix","primary","segment","configureTooltip","strategy","tooltipUrl","host","tip","cleanupAutoUpdate","cleanup","autoUpdate","position","html","r","overlay","popup","closeBtn","content","href","link","centerFallback","rects","vh","padding","placement","computePosition","offset","flip","shift","hide","x","y","middlewareData","center"],"mappings":"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"}
|