astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
80 lines (79 loc) • 3.02 kB
JavaScript
import { mergeConfig as mergeViteConfig } from "vite";
import { arraify, isObject, isURL } from "../util-runtime.js";
function isImageServiceTargets(service) {
return isObject(service) && ("build" in service || "runtime" in service);
}
function hasDefinedValues(value) {
return Object.values(value).some((v) => v !== undefined);
}
function mergeConfigRecursively(defaults, overrides, rootPath) {
const merged = { ...defaults };
for (const key in overrides) {
const value = overrides[key];
if (value == null) {
continue;
}
const existing = merged[key];
if (existing == null) {
merged[key] = value;
continue;
}
// fields that require special handling:
if (key === "vite" && rootPath === "") {
merged[key] = mergeViteConfig(existing, value);
continue;
}
// Merging a single service with `{ build, runtime }` would mix the shapes.
if (key === "service" && rootPath === "image") {
// An override like `{ entrypoint: undefined }` sets nothing, so it shouldn't replace the existing service.
if (isObject(value) && !hasDefinedValues(value)) {
continue;
}
if (isImageServiceTargets(existing) !== isImageServiceTargets(value)) {
merged[key] = value;
continue;
}
}
if (key === "server" && rootPath === "") {
// server config can be a function or an object, if one of the two values is a function,
// create a new wrapper function that merges them
if (typeof existing === "function" || typeof value === "function") {
merged[key] = (...args) => {
const existingConfig = typeof existing === "function" ? existing(...args) : existing;
const valueConfig = typeof value === "function" ? value(...args) : value;
return mergeConfigRecursively(existingConfig, valueConfig, key);
};
continue;
}
}
// for server.allowedHosts, if either value is a boolean, don't merge as arrays
if (key === "allowedHosts" && rootPath === "server" && (typeof existing === "boolean" || typeof value === "boolean")) {
merged[key] = typeof value === "boolean" ? value : existing;
continue;
}
if (Array.isArray(existing) || Array.isArray(value)) {
merged[key] = [...arraify(existing ?? []), ...arraify(value ?? [])];
continue;
}
if (isURL(existing) && isURL(value)) {
merged[key] = value;
continue;
}
// `markdown.processor` is replaced atomically. Deep-merging would mix the inactive
// default's options into the user's chosen processor (e.g. user passes a third-party
// processor, the default unified() `createRenderer` would leak in).
if (key === "processor" && rootPath === "markdown" && isObject(value) && typeof value.createRenderer === "function") {
merged[key] = value;
continue;
}
if (isObject(existing) && isObject(value)) {
merged[key] = mergeConfigRecursively(existing, value, rootPath ? `${rootPath}.${key}` : key);
continue;
}
merged[key] = value;
}
return merged;
}
export function mergeConfig(defaults, overrides) {
return mergeConfigRecursively(defaults, overrides, "");
}