iles
Version:
Vite & Vue powered static site generator with partial hydration
340 lines (337 loc) • 11.6 kB
JavaScript
import {
remarkWrapIslands_default
} from "./chunk-3RBZD6BP.js";
import {
camelCase,
compact,
importLibrary,
isString,
isStringPlugin,
tryImportOrInstallModule,
uncapitalize
} from "./chunk-ROUSHGC2.js";
import {
explicitHtmlPath
} from "./chunk-4HD4NGA3.js";
import {
DIST_CLIENT_PATH,
HYDRATION_DIST_PATH,
ISLAND_COMPONENT_PATH,
resolveAliases
} from "./chunk-TAVOVDVB.js";
// src/node/config.ts
import { promises as fs, existsSync } from "fs";
import { join, resolve } from "pathe";
import pc from "picocolors";
import creatDebugger from "debug";
import { loadConfigFromFile, mergeConfig as mergeViteConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import components from "unplugin-vue-components/vite";
import pages from "@islands/pages";
import mdx from "@islands/mdx";
var debug = creatDebugger("iles:config");
var 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(`\xEEles 'plugins' have been renamed to 'modules'. If you want to provide Vite plugins instead, place them in 'vite:'. Received 'plugins' in ${userConfig.configPath}:
${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((modules2) => modules2.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) {
const solid = await importLibrary("vite-plugin-solid");
return solid({ ssr: true, ...options });
},
async preact(options) {
const preact = await importLibrary("@preact/preset-vite");
return preact(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 \xEEles 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(config, moduleConfig);
if (configFn) {
const partialConfig = await configFn(config, configEnv);
if (partialConfig) config = mergeConfig(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) {
const fn = await tryImportOrInstallModule(pkgName);
return fn(...options);
}
function inferJSX(config) {
const pluginsNested = config.vite?.plugins ?? [];
const plugins = pluginsNested.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: {}
}
},
// Adds lastUpdated meta field.
async extendFrontmatter(frontmatter, filename) {
frontmatter.meta.lastUpdated = (await fs.stat(filename)).mtime;
},
// Adds handling for explicit HTML urls.
extendRoute(route) {
if (appConfig.prettyUrls === false)
route.path = explicitHtmlPath(route.path, route.componentFilename);
},
// Handle 404s in development.
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),
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(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] = mergeViteConfig(existing, value);
else
merged[key] = mergeConfig(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");
}
export {
IlesComponentResolver,
IlesLayoutResolver,
resolveConfig
};