UNPKG

blade

Version:
220 lines (218 loc) • 7.66 kB
import "../../utils-BIjILkWh.js"; import { l as sourceDirPath, o as nodePath, t as composeBuildContext } from "../../build-ByoLfnyM.js"; import path from "node:path"; import resolveFrom from "resolve-from"; import { aliasPlugin } from "rolldown/experimental"; //#region public/server/build.ts const DEPENDENCY_CACHE = /* @__PURE__ */ new Map(); /** * Remaps a package ID based on the provided build configuration. * * @param id - The original package ID. * @param config - The build configuration containing dependency overrides. * * @returns The remapped package ID if an override exists; otherwise, the original ID. */ const overridePackageId = (id, config) => { if (!config.dependencies || !config.dependencies.overrides) return id; const overriddenId = config.dependencies.overrides[id]; if (overriddenId) return overriddenId; for (const [from, to] of Object.entries(config.dependencies.overrides)) if (id === from || id.startsWith(`${from}/`)) return id.replace(from, to); return id; }; /** * Resolves a relative or absolute import path relative to a CDN URL. * * @param importPath - The import path (e.g., './utils', '../foo', or 'lodash'). * @param baseUrl - The base URL to resolve against (e.g., 'https://unpkg.com/react@18.2.0/index.js'). * * @returns The resolved absolute URL. */ const resolveImportPath = (importPath, baseUrl) => { if (importPath.startsWith("http://") || importPath.startsWith("https://")) return importPath; if (importPath.startsWith("./") || importPath.startsWith("../")) return new URL(importPath, baseUrl).href; return `https://unpkg.com/${importPath}`; }; /** * Detects the module type from the URL or content. * * @param url - The URL of the module. * * @returns The module type ('js', 'jsx', 'ts', or 'tsx'). */ const detectModuleType = (url) => { const urlPath = new URL(url).pathname; if (urlPath.endsWith(".tsx")) return "tsx"; if (urlPath.endsWith(".ts")) return "ts"; if (urlPath.endsWith(".jsx")) return "jsx"; return "js"; }; /** * Fetches a dependency from a CDN. * * @param url - The full URL to fetch (e.g., 'https://unpkg.com/react@18.2.0'). * * @returns An object containing the content and the final resolved URL (after redirects). */ const fetchFromCDN = async (url) => { if (DEPENDENCY_CACHE.has(url)) return { content: DEPENDENCY_CACHE.get(url), finalUrl: url }; const response = await fetch(url); if (!response.ok) throw new Error(`Failed to fetch ${url} from CDN`, { cause: response }); const finalUrl = response.url; const content = await response.text(); DEPENDENCY_CACHE.set(url, content); DEPENDENCY_CACHE.set(finalUrl, content); return { content, finalUrl }; }; /** * Set of dependencies that should not be fetched from CDN. * These are resolved from local node_modules via aliases. */ const DEFAULT_EXCLUDED_DEPENDENCIES = new Set([ "react", "react-dom", "scheduler" ]); /** * Checks if an import ID or importer path is from an excluded dependency. * * @param id - The import ID or importer path to check. * * @returns True if the ID is from an excluded dependency. */ const isExcludedDependency = (id, config) => { const excludedDependencies = new Set(config?.dependencies?.external || DEFAULT_EXCLUDED_DEPENDENCIES); if (excludedDependencies.has(id)) return true; for (const excluded of excludedDependencies) { if (id.startsWith(`${excluded}/`)) return true; if (id.includes(`/${excluded}/`) || id.includes(`\\${excluded}\\`)) return true; } return false; }; const makePathAbsolute = (input) => { if (input.startsWith("./")) return input.slice(1); if (input.startsWith("/")) return input; return `/${input}`; }; /** * Composes a list of aliases for the `aliasPlugin` from a `tsconfig.json` file. * * @param config - The content of a `tsconfig.json` file. * * @returns A list of aliases. */ const composeAliases = (config) => { const paths = JSON.parse(config)?.compilerOptions?.paths || {}; return { entries: Object.entries(paths).flatMap(([key, targets]) => { const hasStar = key.includes("*"); const find = hasStar ? /* @__PURE__ */ new RegExp(`^${reEscape(key).replace("\\*", "(.*)")}$`) : key; return targets.map((target) => { const base = makePathAbsolute(target); return { find, replacement: hasStar ? base.replace("*", "$1") : base }; }); }) }; }; const reEscape = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const toPosix = (input) => input.replace(/\\/g, "/"); const ignoreStart = /* @__PURE__ */ new RegExp(`^(?:${[nodePath, sourceDirPath].map((p) => reEscape(toPosix(p))).join("|")})`); /** * Allows for performing an in-memory build. * * @param config - The configuration options for customizing the build behavior. * * @returns The generated build output in the form of virtual files. */ const build = async (config) => { const environment = config?.environment || "development"; const virtualFiles = config.sourceFiles.map(({ path: path$1, content }) => { return { path: makePathAbsolute(path$1), content }; }); const tsconfig = virtualFiles.find((file) => file.path === "/tsconfig.json"); return composeBuildContext(environment, { virtualFiles, plugins: [ ...tsconfig ? [aliasPlugin(composeAliases(tsconfig.content))] : [], { name: "Memory File Loader", resolveId: { filter: { id: { include: [/^\//], exclude: [ignoreStart] } }, handler(id) { const rx = /* @__PURE__ */ new RegExp(`^${reEscape(id)}(?:\\.(?:ts|tsx|js|jsx))?$`); const sourceFile = virtualFiles.find(({ path: path$1 }) => rx.test(path$1)); if (!sourceFile) return; return `virtual:${sourceFile.path}`; } }, load: { filter: { id: /^virtual:/ }, handler(id) { const absolutePath = id.replace("virtual:", ""); const sourceFile = virtualFiles.find(({ path: path$1 }) => path$1 === absolutePath); if (!sourceFile) return; return { code: sourceFile.content, moduleType: path.extname(sourceFile.path).slice(1) }; } } }, { name: "Memory Dependency Loader", resolveId: { filter: { id: [/^[\w@][\w./-]*$/, /^\.\.?\//] }, handler(id, importer) { const resolvedId = overridePackageId(id, config); if (resolvedId.startsWith("blade/")) try { return resolveFrom(nodePath, resolvedId); } catch (error) { console.warn(`Failed to resolve ${resolvedId} from node_modules, falling back to default resolution`, error); return null; } if (isExcludedDependency(resolvedId, config)) return null; if (/-[A-Za-z0-9_-]{8,}\.js$/.test(resolvedId)) return null; if (importer && (resolvedId.startsWith("./") || resolvedId.startsWith("../")) && isExcludedDependency(importer, config)) return null; if (importer?.startsWith("cdn:")) return `cdn:${resolveImportPath(resolvedId, DEPENDENCY_CACHE.get(importer) || importer.replace("cdn:", ""))}`; if (resolvedId.endsWith(".js") && !resolvedId.includes("/")) return null; return `cdn:${resolveImportPath(resolvedId, "https://unpkg.com/")}`; } }, load: { filter: { id: /^cdn:/ }, async handler(id) { const url = id.replace("cdn:", ""); let result; try { result = await fetchFromCDN(url); DEPENDENCY_CACHE.set(id, result.finalUrl); } catch (error) { if (error instanceof Error) throw error; throw new Error(`Failed to fetch dependency: ${url}`, { cause: error }); } return { code: result.content, moduleType: detectModuleType(result.finalUrl) }; } } } ], assetPrefix: config.assetPrefix }); }; //#endregion export { build };