iles
Version:
Vite & Vue powered static site generator with partial hydration
758 lines (757 loc) • 26.2 kB
JavaScript
import { a as DIST_CLIENT_PATH, m as resolveAliases, o as HYDRATION_DIST_PATH, s as ISLAND_COMPONENT_PATH } from "./alias-Cyp8pcW6.mjs";
import { existsSync, promises } from "fs";
import { basename, extname, join, resolve } from "pathe";
import { loadConfigFromFile, mergeConfig } from "vite";
import MagicString from "magic-string";
import pc from "picocolors";
import creatDebugger from "debug";
import vue from "@vitejs/plugin-vue";
import components from "unplugin-vue-components/vite";
import pages from "@islands/pages";
import mdx from "@islands/mdx";
import { constants, promises as promises$1 } from "node:fs";
import process$1 from "node:process";
import { resolve as resolve$1 } from "node:path";
import childProcess from "node:child_process";
import util from "node:util";
import newSpinner from "mico-spinner";
import { detectPackageManager } from "@antfu/install-pkg";
import serialize from "@nuxt/devalue";
import { parse } from "vue/compiler-sfc";
import { init, parse as parse$1 } from "es-module-lexer";
//#region src/node/modules.ts
function unwrapDefault(mod) {
return mod?.default ? unwrapDefault(mod.default) : mod;
}
function slash(path) {
return path.replace(/\\/g, "/");
}
function importModule(path) {
if (process.platform === "win32") {
if (path.match(/^\w:\\/)) return import(`file:///${slash(path)}`).then(unwrapDefault);
if (path.match(/^\w:\//)) return import(`file:///${path}`).then(unwrapDefault);
}
return import(path).then(unwrapDefault);
}
//#endregion
//#region src/node/plugin/utils.ts
const exec = util.promisify(childProcess.exec);
const debug$1 = {
config: creatDebugger("iles:config"),
documents: creatDebugger("iles:documents"),
mdx: creatDebugger("iles:mdx"),
layout: creatDebugger("iles:layout"),
detect: creatDebugger("iles:detect"),
resolve: creatDebugger("iles:resolve"),
build: creatDebugger("iles:build")
};
async function installPackage(names, options = {}) {
const detectedAgent = options.packageManager || await detectPackageManager(options.cwd) || "npm";
const [agent] = detectedAgent.split("@");
if (!Array.isArray(names)) names = [names];
const args = options.additionalArgs || [];
if (options.preferOffline) if (detectedAgent === "yarn@berry") args.unshift("--cached");
else args.unshift("--prefer-offline");
if (agent === "pnpm" && await exists(resolve$1(options.cwd ?? process$1.cwd(), "pnpm-workspace.yaml"))) args.unshift("-w");
const command = `${agent} ${agent === "yarn" ? "add" : "install"} ${options.dev ? "-D" : ""} ${names.join(" ")} ${args.join(" ")}`;
try {
await exec(command, { cwd: options.cwd || process$1.cwd() });
} catch (error) {
const { stderr, stdout } = error;
if (stdout) console.log(stdout);
if (stderr) console.error(stderr);
throw new Error(`Auto-install of ${names.join(" ")} failed, install manually and try again!`);
}
}
async function tryImportOrInstallModule(name) {
try {
return await importModule(name);
} catch (error) {
if (error.code !== "MODULE_NOT_FOUND") throw error;
console.info(`\n${name} not found. Proceeding to auto-install.\n`);
await withSpinner(`Installing ${name}`, async () => await installPackage(name, {
dev: true,
preferOffline: true,
silent: true
}));
return await importModule(name);
}
}
async function importLibrary(pkgName) {
return await tryImportOrInstallModule(pkgName);
}
async function withSpinner(message, fn) {
const spinner = newSpinner(message).start();
try {
const result = await fn();
spinner.succeed();
return result;
} catch (e) {
spinner.fail();
throw e;
}
}
function isString(val) {
return typeof val === "string";
}
function isStringPlugin(val) {
return Array.isArray(val) && isString(val[0]);
}
function uniq(arr) {
return [...new Set(arr.filter((x) => x))];
}
function pascalCase(str) {
return capitalize(camelCase(str));
}
function camelCase(str) {
return str.replace(/[^\w_]+(\w)/g, (_, c) => c ? c.toUpperCase() : "");
}
function uncapitalize(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
async function exists(filePath) {
return await promises$1.access(filePath, constants.F_OK).then(() => true, () => false);
}
function compact(val) {
return val.filter((x) => x);
}
//#endregion
//#region src/node/plugin/parse.ts
function parseId(id) {
const index = id.indexOf("?");
if (index < 0) return {
path: id,
query: {}
};
const query = Object.fromEntries(new URLSearchParams(id.slice(index)));
return {
path: id.slice(0, index),
query
};
}
async function parseExports(code) {
try {
await init;
return parse$1(code)[1].map((spec) => spec.n);
} catch (error) {
console.error(error);
return [];
}
}
async function parseImports(code) {
try {
await init;
const imports = parse$1(code)[0];
const importMap = Object.create(null);
imports.forEach(({ d: isDynamic, n: from, ss: statementStart, s: importPathStart }) => {
if (isDynamic > -1 || !from) return;
parseImportVariables(code.substring(statementStart, importPathStart)).forEach(([name, as = name]) => {
importMap[as] = {
name,
as,
from
};
});
});
return importMap;
} catch (error) {
console.error(error);
return {};
}
}
const importStatementRegex = /import\s*(.*?)\s*from['"\s]+$/s;
const importVarRegex = /(?:\{\s*((?:[^,}]+[,\s]*)+)\}|([^,]+))(?:[,\s]*|\s*$)+/gs;
const trim = (s) => s.trim();
function parseImportVariables(partialStatement) {
const variablesStr = partialStatement.match(importStatementRegex)?.[1].trim();
if (!variablesStr) return [];
return Array.from(variablesStr.matchAll(importVarRegex)).flatMap(([, inBrackets, outer]) => {
if (inBrackets) return inBrackets.split(",").map(trim).filter((x) => x);
outer = outer.trim();
return outer.includes(" as ") ? outer : `default as ${outer}`;
}).map((variable) => variable.split(" as ").map(trim));
}
//#endregion
//#region src/node/plugin/wrap.ts
async function wrapLayout(code, filename) {
const { descriptor: { template }, errors } = parse(code, { filename });
if (errors.length > 0 || !template || !isString(template.attrs.layout)) return;
const s = new MagicString(code);
const nodes = template.ast?.children;
if (!nodes?.length) return;
const Layout = `${pascalCase(template.attrs.layout)}Layout`;
debug$1.layout(`${template.attrs.layout} ${filename}`);
s.appendLeft(nodes[0].loc.start.offset, `<${Layout}>`);
s.appendRight(nodes[nodes.length - 1].loc.end.offset, `</${Layout}>`);
return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
}
const scriptClientRE = /<script\b([^>]*\bclient:[^>]*)>([^]*?)<\/script>/;
async function wrapIslandsInSFC(config, code, filename) {
code = code.replace(scriptClientRE, (_, attrs, content) => `<script-client${attrs}>${content}<\/script-client>`);
const { descriptor: { template, script, scriptSetup, customBlocks }, errors } = parse(code, { filename });
const scriptClientIndex = customBlocks.findIndex((b) => b.type === "script-client");
const scriptClient = scriptClientIndex > -1 && customBlocks[scriptClientIndex];
if (errors.length > 0) return;
if (scriptClient && "setup" in scriptClient.attrs || scriptSetup && Object.keys(scriptSetup.attrs).some((attr) => attr.startsWith("client:"))) throw new Error("Incorrect usage of hydration strategy in script setup.\nSee https://iles-docs.netlify.app/guide/client-scripts#client-script-block");
if (!template?.ast?.children.length) {
if (scriptClient) throw new Error(`Vue components with <script client:...> must define a template containing at least one tag. No valid template found in ${filename}`);
return;
}
const sfcRootNode = template.ast;
const s = new MagicString(code);
const components = config.namedPlugins.components.api;
if (scriptClient) await injectClientScript(sfcRootNode, s, filename, scriptClientIndex, scriptClient);
const jsCode = scriptSetup?.loc?.source || script?.loc?.source;
const imports = jsCode ? await parseImports(jsCode) : {};
let componentCounter = 0;
let injectionOffset = scriptSetup?.loc?.start?.offset;
const elements = sfcRootNode.children.filter((n) => n.tag);
for (const child of elements) await visitSFCNode(child, s, resolveComponentImport);
if (!scriptSetup && injectionOffset === 0) s.appendRight(0, "\n<\/script>\n");
return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
async function resolveComponentImport(strategy, tagName) {
debug$1.detect(`<${tagName} ${strategy}>`);
if (imports[tagName]) return await resolveImportPath(config, imports[tagName], filename);
const info = await resolveComponent(components, tagName, filename, componentCounter++);
if (strategy !== "client:only") injectComponentImport(info);
return info;
}
function injectComponentImport(info) {
if (injectionOffset === void 0) {
const opening = `<script setup lang="${script?.attrs?.lang || "ts"}">`;
s.prepend(opening);
injectionOffset = 0;
}
s.appendRight(injectionOffset, `\n${components.stringifyImport(info)};`);
}
}
async function visitSFCNode(node, s, resolveComponentImport) {
const strategy = "props" in node && node.props.find((prop) => prop.name.startsWith("client:"))?.name;
if (strategy) {
const { tag, loc: { start, end } } = node;
const importMeta = await resolveComponentImport(strategy, tag);
const componentProps = `
:component="${strategy === "client:only" ? null : importMeta.as}"
componentName="${tag}"
importName="${importMeta.name}"
importFrom="${importMeta.from}"
`;
s.overwrite(start.offset + 1, start.offset + 1 + tag.length, `Island ${componentProps.replace(/\n\s*/g, " ")}`, { contentOnly: true });
if (!node.isSelfClosing) s.overwrite(end.offset - 1 - tag.length, end.offset - 1, "Island", { contentOnly: true });
}
if ("children" in node) for (const child of node.children) await visitSFCNode(child, s, resolveComponentImport);
}
async function resolveComponent(components, tag, filename, counter) {
const info = await components.findComponent(pascalCase(tag), filename);
if (!info) throw new Error(`Could not resolve ${tag} in ${filename}. Make sure to import it explicitly, or add a component resolver.`);
return {
name: "default",
...info,
as: `__ile_components_${counter}`
};
}
async function resolveImportPath(config, info, importer) {
info.from = await config.resolvePath(info.from, importer) || info.from;
return info;
}
async function injectClientScript(node, s, filename, index, block) {
const { attrs, content, loc: { end } } = block;
const { lang = "ts", ...props } = attrs;
const importFrom = `${filename}?vue&index=${index}&clientScript=true&lang.${lang}`;
if (!(await parseExports(content)).includes("onLoad")) if (attrs["client:load"] || attrs["client:only"]) s.appendLeft(end.offset, "\nexport const onLoad = undefined\n");
else {
const prettyFilename = filename.slice(Math.max(0, filename.indexOf("src/")));
throw new Error(`Client script in ${prettyFilename} does not export 'onLoad'. Should be a function to execute when the strategy condition is met.`);
}
const elements = node.children.filter((n) => n.tag);
if (elements.length === 1) {
const el = elements[0];
if (!el.props.some((prop) => prop.name === "bind" && prop.loc.source.includes("$attrs"))) s.appendRight(el.loc.start.offset + 1 + el.tag.length, " v-bind=\"$attrs\"");
}
const lastTemplateChildNode = elements[elements.length - 1];
s.appendRight(lastTemplateChildNode.loc.end.offset, `
<Island v-bind='${JSON.stringify({
...props,
component: {},
componentName: "clientScript",
importName: "onLoad",
using: "vanilla",
importFrom
})}'/>`);
}
//#endregion
//#region src/node/plugin/remarkWrapIslands.ts
var remarkWrapIslands_default = ({ config }) => async (ast, file) => {
let components = config.namedPlugins.components.api;
let imports;
let componentPromises = [];
let componentCounter = 0;
const unistUtilVisit = await importModule("unist-util-visit");
const visit = unistUtilVisit.visit || unistUtilVisit;
const SKIP = unistUtilVisit.SKIP;
visit(ast, (node) => {
const strategy = isJsxElement(node) && node.attributes.find(isClientDirective)?.name;
if (strategy) {
wrapWithIsland(strategy, node, resolveComponentImport);
return SKIP;
}
});
const componentsToImport = await Promise.all(componentPromises);
if (componentsToImport.length > 0) ast.children.unshift(defineImports(componentsToImport));
async function resolveComponentImport(strategy, tagName) {
debug$1.detect(`<${tagName} ${strategy}>`);
if (!imports) imports = extractImports(ast.children.filter((node) => node.type === "mdxjsEsm"));
if (imports[tagName]) return await resolveImportPath(config, imports[tagName], file.path);
const info = resolveComponent(components, tagName, file.path, componentCounter++);
if (strategy !== "client:only") componentPromises.push(info);
return await info;
}
};
function isJsxElement(node) {
return node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
}
function isClientDirective(attr) {
return "name" in attr && attr.name.startsWith("client:");
}
function isImport(statement) {
return statement.type === "ImportDeclaration";
}
async function wrapWithIsland(strategy, node, resolveComponentImport) {
const tagName = node.name;
if (!tagName) return;
node.name = "Island";
const importMeta = await resolveComponentImport(strategy, tagName);
node.attributes.unshift(...jsxAttributes({
component: jsxExpression(strategy === "client:only" ? {
type: "Literal",
value: null,
raw: "null"
} : {
type: "Identifier",
name: importMeta.as
}),
componentName: tagName,
importName: importMeta.name,
importFrom: importMeta.from
}));
}
function extractImports(nodes) {
const imports = Object.create(null);
nodes.flatMap((node) => node.data?.estree?.body?.filter(isImport)).forEach(({ specifiers, source: { value: from } }) => {
if (isString(from)) specifiers.forEach((specifier) => {
const as = specifier.local.name;
imports[as] = {
as,
name: importedName(specifier),
from
};
});
});
return imports;
}
function importedName(specifier) {
switch (specifier.type) {
case "ImportDefaultSpecifier": return "default";
case "ImportNamespaceSpecifier": return "*";
default:
if ("name" in specifier.imported) return specifier.imported.name;
throw new Error(`Unpexected literal in import declaration: ${specifier.imported}`);
}
}
function jsxExpression(expression) {
return {
type: "mdxJsxAttributeValueExpression",
value: expression.name || expression.raw,
data: { estree: {
type: "Program",
sourceType: "module",
body: [{
type: "ExpressionStatement",
expression
}]
} }
};
}
function jsxAttributes(val) {
return Object.entries(val).map(([name, value]) => ({
type: "mdxJsxAttribute",
name,
value
}));
}
function defineImports(components) {
return {
type: "mdxjsEsm",
data: { estree: {
type: "Program",
sourceType: "module",
body: components.map((component) => ({
type: "ImportDeclaration",
specifiers: [{
type: "ImportSpecifier",
imported: {
type: "Identifier",
name: component.name
},
local: {
type: "Identifier",
name: component.as
}
}],
source: {
type: "Literal",
value: component.from,
raw: `'${component.from}'`
}
}))
} }
};
}
//#endregion
//#region src/node/utils.ts
function pathToHtmlFilename(path, filename) {
if (extname(path)) return path;
if (!path.endsWith("/") && filename && basename(filename).split(".")[0] === "index") path += "/";
return path + (path.endsWith("/") ? "index.html" : ".html");
}
function explicitHtmlPath(path, filename) {
const htmlFilename = pathToHtmlFilename(path, filename);
return htmlFilename.endsWith("/index.html") ? htmlFilename.replace(/\/index\.html$/, "/") : htmlFilename;
}
//#endregion
//#region src/node/config.ts
const debug = creatDebugger("iles:config");
const IlesComponentResolver = (name) => {
if (name === "Island") return { from: ISLAND_COMPONENT_PATH };
if (name === "Head") return {
name: "Head",
from: "@unhead/vue/components"
};
};
function IlesLayoutResolver(config) {
return (name) => {
const [layoutName, isLayout] = name.split("Layout", 2);
if (layoutName && isLayout === "") {
const layoutFile = join(config.layoutsDir, `${uncapitalize(camelCase(layoutName))}.vue`);
if (existsSync(layoutFile)) return {
name: "default",
from: layoutFile
};
}
};
}
async function resolveConfig(root, env) {
if (!root) root = process.cwd();
if (!env) env = {
mode: "development",
command: "serve",
isSsrBuild: false
};
const appConfig = await resolveUserConfig(root, env);
const srcDir = resolve(root, appConfig.srcDir);
Object.assign(appConfig, {
srcDir,
pagesDir: resolve(srcDir, appConfig.pagesDir),
outDir: resolve(root, appConfig.outDir),
tempDir: resolve(root, appConfig.tempDir),
layoutsDir: resolve(srcDir, appConfig.layoutsDir)
});
for (const mod of appConfig.modules) await mod.configResolved?.(appConfig, env);
appConfig.vite.define["import.meta.env.DISPOSE_ISLANDS"] = env.mode === "development" || appConfig.turbo;
checkDeprecations(appConfig);
return appConfig;
}
async function resolveUserConfig(root, configEnv) {
const config = { root };
const { modules = [], ...userConfig } = await loadUserConfigFile(root, configEnv);
if (userConfig.plugins) throw new Error(`îles 'plugins' have been renamed to 'modules'. If you want to provide Vite plugins instead, place them in 'vite:'. Received 'plugins' in ${userConfig.configPath}:\n${JSON.stringify(userConfig.plugins)}`);
config.modules = compact(await resolveIlesModules([
{
name: "iles:base-config",
...appConfigDefaults(config, userConfig, configEnv)
},
mdx(),
{
name: "user-config",
...userConfig
},
...modules,
pages()
]).then((modules) => modules.flat()));
Object.assign(config, await applyModules(config, configEnv));
await setNamedPlugins(config, configEnv, config.namedPlugins);
const siteUrl = config.siteUrl || "";
const protocolIndex = siteUrl.indexOf("//");
const baseIndex = siteUrl.indexOf("/", protocolIndex > -1 ? protocolIndex + 2 : 0);
config.siteUrl = baseIndex > -1 ? siteUrl.slice(0, baseIndex) : siteUrl;
config.base = baseIndex > -1 ? siteUrl.slice(baseIndex) : "/";
if (!config.base.endsWith("/")) config.base = `${config.base}/`;
config.vite.base = config.base;
config.vite.build.assetsDir = config.assetsDir;
return config;
}
async function loadUserConfigFile(root, configEnv) {
try {
const { path, config = {} } = await loadConfigFromFile(configEnv, "iles.config.ts", root) || {};
if (path && config) {
config.configPath = path;
debug(`loaded config at ${pc.yellow(path)}`);
} else debug("no iles.config.ts file found.");
return config;
} catch (error) {
if (error.message.includes("Could not resolve")) {
debug("no iles.config.ts file found.");
return {};
}
throw error;
}
}
async function setNamedPlugins(config, env, plugins) {
const ceChecks = config.modules.map((mod) => mod.vue?.template?.compilerOptions?.isCustomElement).filter((x) => x);
config.vue.template.compilerOptions.isCustomElement = (tagName) => tagName.startsWith("ile-") || ceChecks.some((fn) => fn(tagName));
plugins.components = components(config.components);
plugins.vue = vue(config.vue);
const optionalPlugins = {
async solid(options) {
return (await importLibrary("vite-plugin-solid"))({
ssr: true,
...options
});
},
async preact(options) {
return (await importLibrary("@preact/preset-vite"))(options);
},
async svelte(options) {
const { svelte } = await importLibrary("@sveltejs/vite-plugin-svelte");
const dev = env.mode === "development";
return svelte({
emitCss: true,
...options,
compilerOptions: {
dev,
...options.compilerOptions
}
});
}
};
for (const [optionName, createPlugin] of Object.entries(optionalPlugins)) {
const addPlugin = config[optionName] || config.jsx === optionName;
if (addPlugin) {
const options = isObject(addPlugin) ? addPlugin : {};
config.vitePlugins.push(await createPlugin(options));
if (optionName === "preact") await tryImportOrInstallModule("preact-render-to-string");
}
}
}
async function applyModules(config, configEnv) {
for (const mod of config.modules) {
if (mod.modules && mod.modules.length > 0) throw new Error(`Modules in îles can't specify the 'modules' option, return an array of modules instead. Found in ${mod.name}: ${JSON.stringify(mod.modules)}`);
const { name, config: configFn, configResolved: _, ...moduleConfig } = mod;
config = mergeConfig$1(config, moduleConfig);
if (configFn) {
const partialConfig = await configFn(config, configEnv);
if (partialConfig) config = mergeConfig$1(config, partialConfig);
}
}
chainModuleCallbacks(config, [
"extendFrontmatter",
"extendRoute",
"extendRoutes"
]);
chainModuleCallbacks(config, [
"beforePageRender",
"onSiteBundled",
"onSiteRendered"
], "ssg");
return config;
}
async function resolveIlesModules(modules) {
return await Promise.all(modules.map(resolveModule));
}
async function resolveModule(mod) {
if (isString(mod)) return await createIlesModule(mod);
if (isStringPlugin(mod)) return await createIlesModule(...mod);
return await mod;
}
async function createIlesModule(pkgName, ...options) {
return (await tryImportOrInstallModule(pkgName))(...options);
}
function inferJSX(config) {
const plugins = (config.vite?.plugins ?? []).flat();
for (const plugin of plugins) {
if (!plugin) continue;
const { name = "" } = plugin;
if (name.includes("preact")) return "preact";
if (name.includes("solid")) return "solid";
}
}
function appConfigDefaults(appConfig, userConfig, env) {
const { root } = appConfig;
const isDevelopment = env.mode === "development";
const { drafts = isDevelopment, jsx = inferJSX(userConfig), srcDir = "src" } = userConfig;
return {
debug: true,
drafts,
turbo: false,
jsx,
root,
base: "/",
siteUrl: "",
prettyUrls: true,
ssg: { sitemap: true },
configPath: resolve(root, "iles.config.ts"),
assetsDir: "assets",
pagesDir: "pages",
srcDir,
outDir: "dist",
layoutsDir: "layouts",
tempDir: ".iles-ssg-temp",
modules: [],
namedPlugins: {},
resolvePath: void 0,
vitePlugins: [],
vite: viteConfigDefaults(root, userConfig),
vue: { template: { compilerOptions: {} } },
async extendFrontmatter(frontmatter, filename) {
frontmatter.meta.lastUpdated = (await promises.stat(filename)).mtime;
},
extendRoute(route) {
if (appConfig.prettyUrls === false) route.path = explicitHtmlPath(route.path, route.componentFilename);
},
extendRoutes(routes) {
if (isDevelopment) return [...routes, {
path: "/:zzz(.*)*",
name: "NotFoundInDev",
componentFilename: "@islands/components/NotFound"
}];
else if (!drafts) return routes.filter((route) => !route.frontmatter?.draft);
},
markdown: {
jsxRuntime: "automatic",
jsxImportSource: "iles",
providerImportSource: "iles",
rehypePlugins: [],
remarkPlugins: [[remarkWrapIslands_default, { get config() {
return appConfig;
} }]]
},
components: {
dts: true,
extensions: [
"vue",
"jsx",
"tsx",
"js",
"ts",
"mdx",
"svelte"
],
include: [
/\.vue$/,
/\.vue\?vue/,
/\.mdx?/
],
dirs: `${srcDir}/components`,
resolvers: [IlesComponentResolver, IlesLayoutResolver(appConfig)],
transformer: "vue3"
}
};
}
function viteConfigDefaults(root, userConfig) {
return {
root,
resolve: {
alias: resolveAliases(root, userConfig),
conditions: [
"module",
"browser",
"development"
],
dedupe: [
"vue",
"vue-router",
"@unhead/vue",
"@vue/devtools-api"
]
},
server: { fs: { allow: [
root,
DIST_CLIENT_PATH,
HYDRATION_DIST_PATH
] } },
build: { cssCodeSplit: false },
define: {},
optimizeDeps: {
include: [
"vue",
"vue-router",
"@unhead/vue",
"@vue/devtools-api"
],
exclude: [
"iles",
"@nuxt/devalue",
"@islands/hydration",
"@islands/prerender",
"vue/server-renderer"
]
}
};
}
function mergeConfig$1(a, b, isRoot = true) {
const merged = { ...a };
for (const key in b) {
const value = b[key];
if (value == null) continue;
const existing = merged[key];
if (Array.isArray(existing) && Array.isArray(value)) {
merged[key] = [...existing, ...value];
continue;
}
if (isObject(existing) && isObject(value)) {
if (isRoot && key === "vite") merged[key] = mergeConfig(existing, value);
else merged[key] = mergeConfig$1(existing, value, false);
continue;
}
merged[key] = value;
}
return merged;
}
function chainModuleCallbacks(config, callbackNames, option) {
callbackNames.forEach((callbackName) => {
const moduleCallbacks = config.modules.map((plugin) => (option ? plugin[option] : plugin)?.[callbackName]).filter((x) => x);
if (moduleCallbacks.length > 0) {
const original = option ? config[option] : config;
original[callbackName] = chainCallbacks(moduleCallbacks);
}
});
}
function chainCallbacks(fns) {
return async (...args) => {
for (let i = 0; i < fns.length; i++) {
const result = await fns[i](...args);
if (result) args[0] = result;
}
return args[0];
};
}
function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
function checkDeprecations(config) {
if (config.markdown?.extendFrontmatter) throw new Error("CHANGES REQUIRED: `markdown.extendFrontmatter` is now `extendFrontmatter`");
if (config.pages?.extendRoute) throw new Error("CHANGES REQUIRED: `pages.extendRoute` is now `extendRoute`");
if (config.pages?.onRoutesGenerated) throw new Error("CHANGES REQUIRED: `pages.onRoutesGenerated` is now `extendRoutes`");
if (config.pages) throw new Error("CHANGES REQUIRED: `pages` is no longer an option, see @islands/pages");
}
//#endregion
export { parseId as a, exists as c, uniq as d, wrapLayout as i, pascalCase as l, pathToHtmlFilename as n, parseImports as o, wrapIslandsInSFC as r, debug$1 as s, resolveConfig as t, serialize as u };