blade
Version:
React at the edge.
220 lines (218 loc) • 7.66 kB
JavaScript
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 };