@withl5e/l5e 1.0.0 → 1.0.1-rc.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/{global-style-BhiGjxVU.js → global-style-Cgno-yNl.js} +2 -2
- package/dist/{global-style-BhiGjxVU.js.map → global-style-Cgno-yNl.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/server-DPBLtja1.js +505 -0
- package/dist/server-DPBLtja1.js.map +1 -0
- package/dist/server.js +1 -1
- package/dist/vite-plugin.js +1 -1
- package/package.json +1 -1
- package/src/core/bundler.ts +30 -49
- package/src/core/script-bundle-policy.ts +77 -0
- package/src/core/server.ts +43 -21
- package/dist/server-5BEwGAL4.js +0 -461
- package/dist/server-5BEwGAL4.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-DPBLtja1.js","names":["serialize"],"sources":["../src/core/bundler.ts","../src/core/script-bundle-policy.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 { InputOptions, OutputChunk, OutputOptions, Plugin } from 'rolldown';\nimport type { ScriptBundlePolicy } from './script-bundle-policy';\n\nlet rolldownModulePromise: Promise<typeof import('rolldown')> | null = null;\n\n/**\n * Resolve Rolldown lazily from the installed framework package. A bare static\n * import can be hoisted when the framework is bundled into a consumer's SSR\n * output, which breaks pnpm's strict dependency layout. Anchoring resolution at\n * @withl5e/l5e keeps the runtime dependency owned by the package that declares it.\n */\nfunction loadRolldown(): Promise<typeof import('rolldown')> {\n if (!rolldownModulePromise) {\n rolldownModulePromise = (async () => {\n const require = createRequire(import.meta.url);\n const frameworkEntry = require.resolve('@withl5e/l5e/server');\n const frameworkRequire = createRequire(frameworkEntry);\n const rolldownPath = frameworkRequire.resolve('rolldown');\n return (await import(\n /* @vite-ignore */ pathToFileURL(rolldownPath).href\n )) as typeof import('rolldown');\n })();\n }\n return rolldownModulePromise;\n}\n\ninterface BundledFile {\n content: string;\n hash: string;\n filename: string;\n mimeType: string;\n}\n\ninterface BundleResult {\n hash: string;\n filename: string;\n content: string;\n}\n\nconst EMPTY_RESULT: BundleResult = { hash: '', filename: '', content: '' };\n\n// Memory map để lưu bundled files\nconst bundledFilesMap = new Map<string, BundledFile>();\n\n/**\n * Single-flight map: cacheKey → promise của lần bundle đang chạy (hoặc đã xong).\n * Vì promise được giữ lại sau khi resolve, map này vừa là in-flight dedup vừa là\n * result cache. Bundle lỗi bị xoá khỏi map để request sau được thử lại.\n */\nconst bundlePromises = new Map<string, Promise<BundleResult>>();\n\n/**\n * Chạy `work` đúng một lần cho mỗi cacheKey, kể cả khi nhiều request đến đồng thời.\n */\nfunction dedupe(cacheKey: string, work: () => Promise<BundleResult>): Promise<BundleResult> {\n const pending = bundlePromises.get(cacheKey);\n if (pending) {\n return pending;\n }\n\n const promise = work().catch((error) => {\n bundlePromises.delete(cacheKey);\n throw error;\n });\n bundlePromises.set(cacheKey, promise);\n return promise;\n}\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// Rolldown coi id bắt đầu bằng \\0 là virtual — nó sẽ không cố đọc từ đĩa.\nconst VIRTUAL_ENTRY_ID = '\\0l5e:bundle-entry';\n\n/**\n * Entry của mỗi lần bundle chỉ là một danh sách import. Giữ nó trong memory thay\n * vì ghi ra đĩa: hai request đồng thời cùng một tập script sinh ra cùng nội dung\n * entry, nên file tạm dùng chung path sẽ bị request này xoá trong lúc Rolldown của\n * request kia còn đang đọc.\n */\nfunction virtualEntryPlugin(entryContent: string): Plugin {\n return {\n name: 'l5e-virtual-entry',\n resolveId(source) {\n return source === VIRTUAL_ENTRY_ID ? VIRTUAL_ENTRY_ID : null;\n },\n load(id) {\n return id === VIRTUAL_ENTRY_ID ? entryContent : null;\n },\n };\n}\n\n/** Inline page roots only; their emitted dependencies retain canonical URLs. */\nfunction preserveBuildChunksPlugin(policy: ScriptBundlePolicy): Plugin {\n return {\n name: 'preserve-build-chunks',\n resolveId(source, importer) {\n if (!importer || source === VIRTUAL_ENTRY_ID) return null;\n const file = path.isAbsolute(source)\n ? path.normalize(source)\n : source.startsWith('.')\n ? path.resolve(path.dirname(importer), source)\n : null;\n if (!file) return null;\n // The virtual entry is the only place where an unshared page entry may\n // be inlined. Never traverse an emitted dependency to re-bundle its body.\n if (importer === VIRTUAL_ENTRY_ID && !policy.isPreserved(file)) return null;\n return { id: policy.assetUrl(file), external: 'absolute' };\n },\n };\n}\n\nasync function runScriptBundle(\n uniquePaths: string[],\n policy: ScriptBundlePolicy,\n): Promise<BundleResult> {\n const entryContent = uniquePaths\n .map((p) => {\n const filePath = policy.fileForScript(p);\n return `import ${JSON.stringify(filePath)};`;\n })\n .join('\\n');\n\n const rolldownOptions: InputOptions = {\n input: VIRTUAL_ENTRY_ID,\n plugins: [virtualEntryPlugin(entryContent), preserveBuildChunksPlugin(policy)],\n platform: 'neutral',\n tsconfig: false,\n // Runtime scripts are imported for their side effects. Do not let an app's\n // package.json sideEffects flag erase those entry imports.\n // Rolldown 1.2 still consults package metadata for boolean `true`, so use\n // an explicit callback to override consumer `sideEffects: false`.\n treeshake: { moduleSideEffects: () => true },\n external: (id) => {\n // External node_modules\n if (!id.startsWith('.') && !path.isAbsolute(id) && id !== VIRTUAL_ENTRY_ID) {\n return true;\n }\n\n // Emitted file imports are resolved by preserveBuildChunksPlugin.\n return false;\n },\n };\n\n const outputOptions: OutputOptions = {\n format: 'es',\n codeSplitting: true,\n minify: false,\n entryFileNames: 'bundle-[hash].js',\n chunkFileNames: 'bundle-[hash].js',\n };\n\n const { rolldown } = await loadRolldown();\n const bundle = await rolldown(rolldownOptions);\n let output;\n try {\n ({ output } = await bundle.generate(outputOptions));\n } finally {\n await bundle.close();\n }\n\n for (const chunk of output) {\n if (chunk.type !== 'chunk') {\n continue;\n }\n bundledFilesMap.set(chunk.fileName, {\n content: chunk.code || '',\n hash: generateHash(chunk.code || ''),\n filename: chunk.fileName,\n mimeType: 'application/javascript',\n });\n }\n\n const entryChunk = output.find(\n (item): item is OutputChunk =>\n item.type === 'chunk' && item.isEntry && item.facadeModuleId === VIRTUAL_ENTRY_ID,\n );\n if (!entryChunk) {\n throw new Error('[bundler] rolldown produced no entry chunk');\n }\n\n return {\n hash: generateHash(entryChunk.code || ''),\n filename: entryChunk.fileName,\n content: entryChunk.code || '',\n };\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 distClientDir: string,\n policy?: ScriptBundlePolicy,\n): Promise<BundleResult> {\n if (scriptPaths.length === 0) {\n return EMPTY_RESULT;\n }\n\n if (!policy || policy.directory !== path.resolve(distClientDir)) {\n console.warn('[bundler] No matching build manifest; serving original script entries.');\n return EMPTY_RESULT;\n }\n\n const uniquePaths = [...new Set(scriptPaths)].sort();\n const cacheKey = `scripts:${policy.cacheKey}:${JSON.stringify(uniquePaths)}`;\n\n try {\n return await dedupe(cacheKey, () => runScriptBundle(uniquePaths, policy));\n } catch (error) {\n console.error('[bundler] Error bundling scripts:', error);\n return EMPTY_RESULT;\n }\n}\n\nasync function runCssBundle(uniquePaths: string[], distClientDir: string): Promise<BundleResult> {\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 bundledFilesMap.set(filename, {\n content: bundledContent,\n hash,\n filename,\n mimeType: 'text/css',\n });\n\n return { hash, filename, content: bundledContent };\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(cssPaths: string[], distClientDir: string): Promise<BundleResult> {\n if (cssPaths.length === 0) {\n return EMPTY_RESULT;\n }\n\n const uniquePaths = [...new Set(cssPaths)].sort();\n const cacheKey = `css:${JSON.stringify([path.resolve(distClientDir), uniquePaths])}`;\n\n try {\n return await dedupe(cacheKey, () => runCssBundle(uniquePaths, distClientDir));\n } catch (error) {\n console.error('[bundler] Error bundling CSS:', error);\n return EMPTY_RESULT;\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 bundlePromises.clear();\n}\n","import { createHash } from 'node:crypto';\nimport { realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport type { Manifest } from 'vite';\nimport { withAssetBase } from './global-style';\n\n/** Preserve the emitted module boundaries chosen by the application's build. */\nexport function createScriptBundlePolicy(manifest: Manifest, distClientDir: string, base = '/') {\n const directory = path.resolve(distClientDir);\n // Rolldown resolves entry importers through symlinks/junctions. Use the same\n // physical directory for identity while retaining the caller's output scope.\n const assetDirectory = realpathSync(directory);\n const files = new Set<string>();\n const preserved = new Set<string>();\n const jsEntries = Object.entries(manifest).filter(([, entry]) => /\\.[cm]?js$/.test(entry.file));\n\n function absoluteFile(file: string) {\n const absolute = path.resolve(assetDirectory, file);\n const relative = path.relative(assetDirectory, absolute);\n if (\n !relative ||\n relative === '..' ||\n relative.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relative)\n ) {\n throw new Error(`[bundler] Asset is outside the client output: ${file}`);\n }\n return absolute;\n }\n\n for (const [key, entry] of jsEntries) {\n const file = absoluteFile(entry.file);\n files.add(file);\n if (\n !entry.isEntry ||\n entry.isDynamicEntry ||\n key === 'src/client.global.ts' ||\n /(?:^|\\/)react\\//.test(entry.src || key)\n )\n preserved.add(file);\n }\n\n // Index the whole build, not just this request's roots. This also protects an\n // entry imported by another entry, even when both are selected on this page.\n for (const [, entry] of jsEntries) {\n for (const key of [...(entry.imports || []), ...(entry.dynamicImports || [])]) {\n const dependency = manifest[key];\n if (!dependency || !files.has(absoluteFile(dependency.file))) {\n throw new Error(`[bundler] Missing JavaScript manifest dependency: ${key}`);\n }\n preserved.add(absoluteFile(dependency.file));\n }\n }\n\n const cacheKey = createHash('sha256')\n .update(JSON.stringify([directory, assetDirectory, base, manifest]))\n .digest('hex');\n\n return {\n cacheKey,\n directory,\n fileForScript(script: string) {\n const file = absoluteFile(script.replace(/^\\/+/, ''));\n if (!files.has(file)) throw new Error(`[bundler] Script is missing from manifest: ${script}`);\n return file;\n },\n isPreserved(file: string) {\n return preserved.has(file);\n },\n assetUrl(file: string) {\n if (!files.has(file)) throw new Error(`[bundler] Import is missing from manifest: ${file}`);\n return withAssetBase(base, path.relative(assetDirectory, file).replace(/\\\\/g, '/'));\n },\n };\n}\n\nexport type ScriptBundlePolicy = ReturnType<typeof createScriptBundlePolicy>;\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 { resolveGlobalStyleHref, withAssetBase } from './global-style';\nimport { createScriptBundlePolicy, type ScriptBundlePolicy } from './script-bundle-policy';\nimport type { Manifest } from 'vite';\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 * Externalize React island props into a single `<script id=\"_l5e_data_\">` at\n * the end of the document (referenced by `data-island-idx`) instead of inlining\n * a large `data-island-props` attribute on each element — keeps the SSR HTML\n * lean so crawlers read content first. Default true; set false for the legacy\n * inline behavior.\n */\n externalizeIslandProps?: boolean;\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: (\n url: string,\n requestInfo?: RequestInfo,\n options?: { externalizeIslandProps?: boolean },\n ) => 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 assetBase,\n scriptPolicy,\n}: {\n rendered: RenderResult;\n template: string;\n manifest?: Record<string, any>;\n root: string;\n distClientDir: string;\n isProduction: boolean;\n assetBase: string;\n scriptPolicy?: ScriptBundlePolicy;\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 const emittedStyles = new Set<string>();\n const appendStylesheet = (href: string, crossorigin = false) => {\n if (emittedStyles.has(href)) return;\n emittedStyles.add(href);\n extraHead += `<link rel=\"stylesheet\"${crossorigin ? ' crossorigin' : ''} href=\"${escapeProp(href)}\">`;\n };\n let globalScripts: string[] = [];\n let islandRegistryScript = '';\n\n const globalStyleHref = resolveGlobalStyleHref({\n root,\n manifest,\n isProduction,\n base: assetBase,\n });\n if (globalStyleHref) appendStylesheet(globalStyleHref, isProduction);\n\n // Externalized island props → a single JSON script at the end of the document.\n // serialize-javascript with isJSON escapes `<`, `>`, `&` (and U+2028/2029) so the\n // props can't break out of the <script> block or inject markup (XSS). The runtime\n // reads it via `document.getElementById('_l5e_data_')` + JSON.parse.\n let islandDataScript = '';\n if (rendered.islandData && rendered.islandData.length > 0) {\n islandDataScript = `<script type=\"application/json\" id=\"_l5e_data_\">${serialize(\n rendered.islandData,\n { isJSON: true },\n )}</script>`;\n }\n\n if (isProduction && manifest) {\n scriptSrcList = scriptSrcList.filter(\n (src) => src.replace(/^\\//, '') !== 'src/client.global.ts',\n );\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, distClientDir, scriptPolicy);\n scriptSrcList = bundledScript.filename\n ? [withAssetBase(assetBase, bundledScript.filename)]\n : mappedScripts.map((file) => withAssetBase(assetBase, file));\n }\n\n if (mappedCssFiles.length > 0) {\n const bundledCss = await bundleCss(mappedCssFiles, distClientDir);\n if (bundledCss.filename) {\n cssSrcList = [withAssetBase(assetBase, 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 appendStylesheet(withAssetBase(assetBase, cssFile), true);\n });\n }\n if (globalEntry.file) {\n globalScripts.push(withAssetBase(assetBase, 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] = withAssetBase(assetBase, 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 cssSrcList.forEach((file) => appendStylesheet(file, true));\n }\n }\n\n if (!isProduction) {\n // Registry đã dedupe, nhưng vẫn lọc lại ở đây để dev không bao giờ ra thẻ trùng\n [...new Set(cssSrcList)].forEach((src) => appendStylesheet(src));\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 // useClientJs('/src/client.global.ts') do user tự gọi sẽ trùng với entry\n // được prepend ở trên — registry không bắt được ca này nên dedupe lại\n allScripts = [...new Set(allScripts)];\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 islandDataScript +\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)\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[] = [];\n const cdnCacheControlParts: string[] = [];\n if (maxAge !== undefined) {\n cacheControlParts.push(`max-age=${maxAge}`);\n cdnCacheControlParts.push(`max-age=${maxAge}`);\n }\n\n if (sMaxAge !== undefined) {\n cdnCacheControlParts.push(`s-maxage=${sMaxAge}`);\n }\n\n // ko cho phép browser swr\n if (swr !== undefined) {\n cdnCacheControlParts.push(`stale-while-revalidate=${swr}`);\n }\n\n if ((cacheControlParts.length > 0 || cdnCacheControlParts.length > 0) && isProduction) {\n cacheControlParts.push('public');\n cacheControlParts.push('must-revalidate');\n\n cdnCacheControlParts.push('public');\n\n headers.set('Cache-Control', cacheControlParts.join(', '));\n headers.set('CDN-Cache-Control', cdnCacheControlParts.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 const externalizeIslandProps = options.externalizeIslandProps ?? true;\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 // A production server owns one immutable build. Share its manifest/policy\n // across requests, just like its cached HTML template and SSR module.\n const productionManifest: Manifest | undefined = isProduction\n ? JSON.parse(await fs.readFile(path.join(distClientDir, '.vite/manifest.json'), 'utf-8'))\n : undefined;\n let scriptPolicy: ScriptBundlePolicy | undefined;\n if (productionManifest) {\n try {\n scriptPolicy = createScriptBundlePolicy(productionManifest, distClientDir, base);\n } catch (error) {\n console.warn('[bundler] Invalid build manifest; serving original script entries.', error);\n }\n }\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 withAssetBase(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'\n ? loadedMiddleware\n : (_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(\n payload,\n middlewareContext.request,\n middlewareContext.url,\n );\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: (\n url: string,\n requestInfo?: any,\n options?: { externalizeIslandProps?: boolean },\n ) => 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 manifest = productionManifest;\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 externalizeIslandProps,\n });\n return createPageResponse({\n rendered: nextRendered,\n template,\n manifest,\n root,\n distClientDir,\n isProduction,\n assetBase: isProduction ? base : vite!.config.base,\n scriptPolicy,\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"],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, i as t, n, r, t as i } from "./server-
|
|
1
|
+
import { a as e, i as t, n, r, t as i } from "./server-DPBLtja1.js";
|
|
2
2
|
export { i as applyHtmlLang, n as createServer, r as hashTag, t as optimizeCacheTags, e as startServer };
|
package/dist/vite-plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as e } from "./global-style-
|
|
1
|
+
import { t as e } from "./global-style-Cgno-yNl.js";
|
|
2
2
|
import { existsSync as t, mkdirSync as n, readFileSync as r, readdirSync as i, rmSync as a, statSync as o, writeFileSync as s } from "fs";
|
|
3
3
|
import { transform as c } from "esbuild";
|
|
4
4
|
import { dirname as l, join as u, relative as d, resolve as f } from "path";
|
package/package.json
CHANGED
package/src/core/bundler.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
import type { InputOptions, OutputChunk, OutputOptions, Plugin } from 'rolldown';
|
|
8
|
+
import type { ScriptBundlePolicy } from './script-bundle-policy';
|
|
8
9
|
|
|
9
10
|
let rolldownModulePromise: Promise<typeof import('rolldown')> | null = null;
|
|
10
11
|
|
|
@@ -99,60 +100,40 @@ function virtualEntryPlugin(entryContent: string): Plugin {
|
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
* Global files (*.global.*) đã được client.global.ts load — bundle lại sẽ tạo
|
|
105
|
-
* module instance trùng (vd nanostores).
|
|
106
|
-
*/
|
|
107
|
-
function vendorPathRewriterPlugin(distClientDir: string): Plugin {
|
|
108
|
-
const toWebPath = (absolutePath: string) =>
|
|
109
|
-
'/' + path.relative(distClientDir, absolutePath).replace(/\\/g, '/');
|
|
110
|
-
|
|
103
|
+
/** Inline page roots only; their emitted dependencies retain canonical URLs. */
|
|
104
|
+
function preserveBuildChunksPlugin(policy: ScriptBundlePolicy): Plugin {
|
|
111
105
|
return {
|
|
112
|
-
name: '
|
|
106
|
+
name: 'preserve-build-chunks',
|
|
113
107
|
resolveId(source, importer) {
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (importer && source.startsWith('.')) {
|
|
128
|
-
// Relative path như ./auth.global-BOVr81Z5.js — resolve từ importer
|
|
129
|
-
return {
|
|
130
|
-
id: toWebPath(path.resolve(path.dirname(importer), source)),
|
|
131
|
-
external: 'absolute',
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return null;
|
|
108
|
+
if (!importer || source === VIRTUAL_ENTRY_ID) return null;
|
|
109
|
+
const file = path.isAbsolute(source)
|
|
110
|
+
? path.normalize(source)
|
|
111
|
+
: source.startsWith('.')
|
|
112
|
+
? path.resolve(path.dirname(importer), source)
|
|
113
|
+
: null;
|
|
114
|
+
if (!file) return null;
|
|
115
|
+
// The virtual entry is the only place where an unshared page entry may
|
|
116
|
+
// be inlined. Never traverse an emitted dependency to re-bundle its body.
|
|
117
|
+
if (importer === VIRTUAL_ENTRY_ID && !policy.isPreserved(file)) return null;
|
|
118
|
+
return { id: policy.assetUrl(file), external: 'absolute' };
|
|
136
119
|
},
|
|
137
120
|
};
|
|
138
121
|
}
|
|
139
122
|
|
|
140
123
|
async function runScriptBundle(
|
|
141
124
|
uniquePaths: string[],
|
|
142
|
-
|
|
125
|
+
policy: ScriptBundlePolicy,
|
|
143
126
|
): Promise<BundleResult> {
|
|
144
127
|
const entryContent = uniquePaths
|
|
145
128
|
.map((p) => {
|
|
146
|
-
const filePath =
|
|
147
|
-
? path.join(distClientDir, p.substring(1))
|
|
148
|
-
: path.join(distClientDir, p);
|
|
129
|
+
const filePath = policy.fileForScript(p);
|
|
149
130
|
return `import ${JSON.stringify(filePath)};`;
|
|
150
131
|
})
|
|
151
132
|
.join('\n');
|
|
152
133
|
|
|
153
134
|
const rolldownOptions: InputOptions = {
|
|
154
135
|
input: VIRTUAL_ENTRY_ID,
|
|
155
|
-
plugins: [virtualEntryPlugin(entryContent),
|
|
136
|
+
plugins: [virtualEntryPlugin(entryContent), preserveBuildChunksPlugin(policy)],
|
|
156
137
|
platform: 'neutral',
|
|
157
138
|
tsconfig: false,
|
|
158
139
|
// Runtime scripts are imported for their side effects. Do not let an app's
|
|
@@ -166,7 +147,7 @@ async function runScriptBundle(
|
|
|
166
147
|
return true;
|
|
167
148
|
}
|
|
168
149
|
|
|
169
|
-
//
|
|
150
|
+
// Emitted file imports are resolved by preserveBuildChunksPlugin.
|
|
170
151
|
return false;
|
|
171
152
|
},
|
|
172
153
|
};
|
|
@@ -222,26 +203,29 @@ async function runScriptBundle(
|
|
|
222
203
|
export async function bundleScripts(
|
|
223
204
|
scriptPaths: string[],
|
|
224
205
|
distClientDir: string,
|
|
206
|
+
policy?: ScriptBundlePolicy,
|
|
225
207
|
): Promise<BundleResult> {
|
|
226
208
|
if (scriptPaths.length === 0) {
|
|
227
209
|
return EMPTY_RESULT;
|
|
228
210
|
}
|
|
229
211
|
|
|
212
|
+
if (!policy || policy.directory !== path.resolve(distClientDir)) {
|
|
213
|
+
console.warn('[bundler] No matching build manifest; serving original script entries.');
|
|
214
|
+
return EMPTY_RESULT;
|
|
215
|
+
}
|
|
216
|
+
|
|
230
217
|
const uniquePaths = [...new Set(scriptPaths)].sort();
|
|
231
|
-
const cacheKey = `scripts:${
|
|
218
|
+
const cacheKey = `scripts:${policy.cacheKey}:${JSON.stringify(uniquePaths)}`;
|
|
232
219
|
|
|
233
220
|
try {
|
|
234
|
-
return await dedupe(cacheKey, () => runScriptBundle(uniquePaths,
|
|
221
|
+
return await dedupe(cacheKey, () => runScriptBundle(uniquePaths, policy));
|
|
235
222
|
} catch (error) {
|
|
236
223
|
console.error('[bundler] Error bundling scripts:', error);
|
|
237
224
|
return EMPTY_RESULT;
|
|
238
225
|
}
|
|
239
226
|
}
|
|
240
227
|
|
|
241
|
-
async function runCssBundle(
|
|
242
|
-
uniquePaths: string[],
|
|
243
|
-
distClientDir: string,
|
|
244
|
-
): Promise<BundleResult> {
|
|
228
|
+
async function runCssBundle(uniquePaths: string[], distClientDir: string): Promise<BundleResult> {
|
|
245
229
|
const cssContents: string[] = [];
|
|
246
230
|
|
|
247
231
|
for (const cssPath of uniquePaths) {
|
|
@@ -276,16 +260,13 @@ async function runCssBundle(
|
|
|
276
260
|
* Bundle CSS files từ dist/client thành 1 file
|
|
277
261
|
* Trong production, các file đã được build sẵn trong dist/client
|
|
278
262
|
*/
|
|
279
|
-
export async function bundleCss(
|
|
280
|
-
cssPaths: string[],
|
|
281
|
-
distClientDir: string,
|
|
282
|
-
): Promise<BundleResult> {
|
|
263
|
+
export async function bundleCss(cssPaths: string[], distClientDir: string): Promise<BundleResult> {
|
|
283
264
|
if (cssPaths.length === 0) {
|
|
284
265
|
return EMPTY_RESULT;
|
|
285
266
|
}
|
|
286
267
|
|
|
287
268
|
const uniquePaths = [...new Set(cssPaths)].sort();
|
|
288
|
-
const cacheKey = `css:${
|
|
269
|
+
const cacheKey = `css:${JSON.stringify([path.resolve(distClientDir), uniquePaths])}`;
|
|
289
270
|
|
|
290
271
|
try {
|
|
291
272
|
return await dedupe(cacheKey, () => runCssBundle(uniquePaths, distClientDir));
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { Manifest } from 'vite';
|
|
5
|
+
import { withAssetBase } from './global-style';
|
|
6
|
+
|
|
7
|
+
/** Preserve the emitted module boundaries chosen by the application's build. */
|
|
8
|
+
export function createScriptBundlePolicy(manifest: Manifest, distClientDir: string, base = '/') {
|
|
9
|
+
const directory = path.resolve(distClientDir);
|
|
10
|
+
// Rolldown resolves entry importers through symlinks/junctions. Use the same
|
|
11
|
+
// physical directory for identity while retaining the caller's output scope.
|
|
12
|
+
const assetDirectory = realpathSync(directory);
|
|
13
|
+
const files = new Set<string>();
|
|
14
|
+
const preserved = new Set<string>();
|
|
15
|
+
const jsEntries = Object.entries(manifest).filter(([, entry]) => /\.[cm]?js$/.test(entry.file));
|
|
16
|
+
|
|
17
|
+
function absoluteFile(file: string) {
|
|
18
|
+
const absolute = path.resolve(assetDirectory, file);
|
|
19
|
+
const relative = path.relative(assetDirectory, absolute);
|
|
20
|
+
if (
|
|
21
|
+
!relative ||
|
|
22
|
+
relative === '..' ||
|
|
23
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
24
|
+
path.isAbsolute(relative)
|
|
25
|
+
) {
|
|
26
|
+
throw new Error(`[bundler] Asset is outside the client output: ${file}`);
|
|
27
|
+
}
|
|
28
|
+
return absolute;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const [key, entry] of jsEntries) {
|
|
32
|
+
const file = absoluteFile(entry.file);
|
|
33
|
+
files.add(file);
|
|
34
|
+
if (
|
|
35
|
+
!entry.isEntry ||
|
|
36
|
+
entry.isDynamicEntry ||
|
|
37
|
+
key === 'src/client.global.ts' ||
|
|
38
|
+
/(?:^|\/)react\//.test(entry.src || key)
|
|
39
|
+
)
|
|
40
|
+
preserved.add(file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Index the whole build, not just this request's roots. This also protects an
|
|
44
|
+
// entry imported by another entry, even when both are selected on this page.
|
|
45
|
+
for (const [, entry] of jsEntries) {
|
|
46
|
+
for (const key of [...(entry.imports || []), ...(entry.dynamicImports || [])]) {
|
|
47
|
+
const dependency = manifest[key];
|
|
48
|
+
if (!dependency || !files.has(absoluteFile(dependency.file))) {
|
|
49
|
+
throw new Error(`[bundler] Missing JavaScript manifest dependency: ${key}`);
|
|
50
|
+
}
|
|
51
|
+
preserved.add(absoluteFile(dependency.file));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const cacheKey = createHash('sha256')
|
|
56
|
+
.update(JSON.stringify([directory, assetDirectory, base, manifest]))
|
|
57
|
+
.digest('hex');
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
cacheKey,
|
|
61
|
+
directory,
|
|
62
|
+
fileForScript(script: string) {
|
|
63
|
+
const file = absoluteFile(script.replace(/^\/+/, ''));
|
|
64
|
+
if (!files.has(file)) throw new Error(`[bundler] Script is missing from manifest: ${script}`);
|
|
65
|
+
return file;
|
|
66
|
+
},
|
|
67
|
+
isPreserved(file: string) {
|
|
68
|
+
return preserved.has(file);
|
|
69
|
+
},
|
|
70
|
+
assetUrl(file: string) {
|
|
71
|
+
if (!files.has(file)) throw new Error(`[bundler] Import is missing from manifest: ${file}`);
|
|
72
|
+
return withAssetBase(base, path.relative(assetDirectory, file).replace(/\\/g, '/'));
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type ScriptBundlePolicy = ReturnType<typeof createScriptBundlePolicy>;
|
package/src/core/server.ts
CHANGED
|
@@ -9,7 +9,9 @@ import type { ViteDevServer } from 'vite';
|
|
|
9
9
|
import { createContext, type MiddlewareHandler, type RewritePayload } from '../middleware';
|
|
10
10
|
import { bundleCss, bundleScripts, getBundledFile } from './bundler';
|
|
11
11
|
import type { RenderResult, RequestInfo } from './entry-server';
|
|
12
|
-
import { resolveGlobalStyleHref } from './global-style';
|
|
12
|
+
import { resolveGlobalStyleHref, withAssetBase } from './global-style';
|
|
13
|
+
import { createScriptBundlePolicy, type ScriptBundlePolicy } from './script-bundle-policy';
|
|
14
|
+
import type { Manifest } from 'vite';
|
|
13
15
|
import { escapeProp } from './render';
|
|
14
16
|
import { createHeadersFromExpressRequest, parseCookies } from './request';
|
|
15
17
|
|
|
@@ -215,6 +217,7 @@ async function createPageResponse({
|
|
|
215
217
|
distClientDir,
|
|
216
218
|
isProduction,
|
|
217
219
|
assetBase,
|
|
220
|
+
scriptPolicy,
|
|
218
221
|
}: {
|
|
219
222
|
rendered: RenderResult;
|
|
220
223
|
template: string;
|
|
@@ -223,6 +226,7 @@ async function createPageResponse({
|
|
|
223
226
|
distClientDir: string;
|
|
224
227
|
isProduction: boolean;
|
|
225
228
|
assetBase: string;
|
|
229
|
+
scriptPolicy?: ScriptBundlePolicy;
|
|
226
230
|
}): Promise<globalThis.Response> {
|
|
227
231
|
const rawResponse = createRawResponse(rendered);
|
|
228
232
|
if (rawResponse) {
|
|
@@ -277,7 +281,9 @@ async function createPageResponse({
|
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
if (isProduction && manifest) {
|
|
280
|
-
scriptSrcList = scriptSrcList.filter(
|
|
284
|
+
scriptSrcList = scriptSrcList.filter(
|
|
285
|
+
(src) => src.replace(/^\//, '') !== 'src/client.global.ts',
|
|
286
|
+
);
|
|
281
287
|
cssSrcList = cssSrcList.filter((src) => !src.includes('.global.'));
|
|
282
288
|
|
|
283
289
|
const cssFiles = new Set<string>();
|
|
@@ -326,14 +332,16 @@ async function createPageResponse({
|
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
if (mappedScripts.length > 0) {
|
|
329
|
-
const bundledScript = await bundleScripts(mappedScripts, distClientDir);
|
|
330
|
-
scriptSrcList = bundledScript.filename
|
|
335
|
+
const bundledScript = await bundleScripts(mappedScripts, distClientDir, scriptPolicy);
|
|
336
|
+
scriptSrcList = bundledScript.filename
|
|
337
|
+
? [withAssetBase(assetBase, bundledScript.filename)]
|
|
338
|
+
: mappedScripts.map((file) => withAssetBase(assetBase, file));
|
|
331
339
|
}
|
|
332
340
|
|
|
333
341
|
if (mappedCssFiles.length > 0) {
|
|
334
342
|
const bundledCss = await bundleCss(mappedCssFiles, distClientDir);
|
|
335
343
|
if (bundledCss.filename) {
|
|
336
|
-
cssSrcList = [
|
|
344
|
+
cssSrcList = [withAssetBase(assetBase, bundledCss.filename)];
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
347
|
|
|
@@ -341,11 +349,11 @@ async function createPageResponse({
|
|
|
341
349
|
if (globalEntry) {
|
|
342
350
|
if (globalEntry.css && globalEntry.css.length > 0) {
|
|
343
351
|
globalEntry.css.forEach((cssFile: string) => {
|
|
344
|
-
appendStylesheet(
|
|
352
|
+
appendStylesheet(withAssetBase(assetBase, cssFile), true);
|
|
345
353
|
});
|
|
346
354
|
}
|
|
347
355
|
if (globalEntry.file) {
|
|
348
|
-
globalScripts.push(
|
|
356
|
+
globalScripts.push(withAssetBase(assetBase, globalEntry.file));
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
359
|
|
|
@@ -354,7 +362,7 @@ async function createPageResponse({
|
|
|
354
362
|
for (const island of islandEntries) {
|
|
355
363
|
const entry = manifest[island.src];
|
|
356
364
|
if (entry?.file) {
|
|
357
|
-
islandMap[island.key] =
|
|
365
|
+
islandMap[island.key] = withAssetBase(assetBase, entry.file);
|
|
358
366
|
}
|
|
359
367
|
}
|
|
360
368
|
if (Object.keys(islandMap).length > 0) {
|
|
@@ -406,9 +414,9 @@ async function createPageResponse({
|
|
|
406
414
|
const html = rendered.rawHtml
|
|
407
415
|
? rendered.html || ''
|
|
408
416
|
: templateWithLang
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
417
|
+
.replace(`<!--app-head-->`, () => (rendered.head ?? '') + extraHead)
|
|
418
|
+
.replace(`<!--app-html-->`, () => rendered.html ?? '')
|
|
419
|
+
.replace(`<!--app-scripts-->`, () => scriptsHtml);
|
|
412
420
|
|
|
413
421
|
const headers = new Headers({
|
|
414
422
|
'Content-Type': 'text/html',
|
|
@@ -436,7 +444,6 @@ async function createPageResponse({
|
|
|
436
444
|
|
|
437
445
|
cdnCacheControlParts.push('public');
|
|
438
446
|
|
|
439
|
-
|
|
440
447
|
headers.set('Cache-Control', cacheControlParts.join(', '));
|
|
441
448
|
headers.set('CDN-Cache-Control', cdnCacheControlParts.join(', '));
|
|
442
449
|
}
|
|
@@ -482,6 +489,19 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
482
489
|
// Add Vite or respective production middlewares
|
|
483
490
|
let vite: ViteDevServer | undefined;
|
|
484
491
|
const distClientDir = path.join(root, './dist/client');
|
|
492
|
+
// A production server owns one immutable build. Share its manifest/policy
|
|
493
|
+
// across requests, just like its cached HTML template and SSR module.
|
|
494
|
+
const productionManifest: Manifest | undefined = isProduction
|
|
495
|
+
? JSON.parse(await fs.readFile(path.join(distClientDir, '.vite/manifest.json'), 'utf-8'))
|
|
496
|
+
: undefined;
|
|
497
|
+
let scriptPolicy: ScriptBundlePolicy | undefined;
|
|
498
|
+
if (productionManifest) {
|
|
499
|
+
try {
|
|
500
|
+
scriptPolicy = createScriptBundlePolicy(productionManifest, distClientDir, base);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.warn('[bundler] Invalid build manifest; serving original script entries.', error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
485
505
|
|
|
486
506
|
if (!isProduction) {
|
|
487
507
|
const { createServer } = await import('vite');
|
|
@@ -516,7 +536,7 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
516
536
|
// Route để serve bundled files từ memory map
|
|
517
537
|
// Äặt route nà y trước route HTML để catch request trước
|
|
518
538
|
app.get(
|
|
519
|
-
|
|
539
|
+
withAssetBase(base, 'bundle-:hash.:ext'),
|
|
520
540
|
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
521
541
|
try {
|
|
522
542
|
const { hash, ext } = req.params;
|
|
@@ -670,13 +690,19 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
670
690
|
|
|
671
691
|
const loadedMiddleware = await entryServerModule.loadMiddleware?.();
|
|
672
692
|
const middlewareHandler: MiddlewareHandler =
|
|
673
|
-
typeof loadedMiddleware === 'function'
|
|
693
|
+
typeof loadedMiddleware === 'function'
|
|
694
|
+
? loadedMiddleware
|
|
695
|
+
: (_ctx, dummyNext) => dummyNext();
|
|
674
696
|
|
|
675
697
|
// If middleware short-circuits (returns its own Response — a redirect, a
|
|
676
698
|
// 403, ...) instead of calling `next`, this callback never runs and that
|
|
677
699
|
// response wins, same as it would for a page request.
|
|
678
700
|
const response = await middlewareHandler(middlewareContext, async (payload) => {
|
|
679
|
-
const nextRequest = createRewriteRequest(
|
|
701
|
+
const nextRequest = createRewriteRequest(
|
|
702
|
+
payload,
|
|
703
|
+
middlewareContext.request,
|
|
704
|
+
middlewareContext.url,
|
|
705
|
+
);
|
|
680
706
|
const requestInfo = {
|
|
681
707
|
...createRequestInfo(req, nextRequest, base, locals),
|
|
682
708
|
path: req.originalUrl,
|
|
@@ -743,12 +769,7 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
743
769
|
)) as EntryServerModule;
|
|
744
770
|
render = entryServer.render;
|
|
745
771
|
loadMiddleware = entryServer.loadMiddleware;
|
|
746
|
-
|
|
747
|
-
const manifestJson = await fs.readFile(
|
|
748
|
-
path.join(root, './dist/client/.vite/manifest.json'),
|
|
749
|
-
'utf-8',
|
|
750
|
-
);
|
|
751
|
-
manifest = JSON.parse(manifestJson);
|
|
772
|
+
manifest = productionManifest;
|
|
752
773
|
}
|
|
753
774
|
|
|
754
775
|
const loadedMiddleware = await loadMiddleware?.();
|
|
@@ -778,6 +799,7 @@ export async function createServer(options: ServerOptions = {}): Promise<ServerC
|
|
|
778
799
|
distClientDir,
|
|
779
800
|
isProduction,
|
|
780
801
|
assetBase: isProduction ? base : vite!.config.base,
|
|
802
|
+
scriptPolicy,
|
|
781
803
|
});
|
|
782
804
|
};
|
|
783
805
|
|