iles
Version:
Vite & Vue powered static site generator with partial hydration
344 lines (337 loc) • 12 kB
JavaScript
import {
detectMDXComponents
} from "./chunk-I7KKP3JN.js";
import {
extendSite
} from "./chunk-RU6IEHIA.js";
import {
autoImportComposables,
writeComposablesDTS
} from "./chunk-EOT5JKVD.js";
import {
documentsPlugin
} from "./chunk-AP7ID5PL.js";
import {
resolveConfig
} from "./chunk-T4WFAR6L.js";
import {
wrapIslandsInSFC,
wrapLayout
} from "./chunk-CZOBZ67L.js";
import {
parseId
} from "./chunk-6FAOHHHS.js";
import {
debug,
default as default2,
exists,
pascalCase
} from "./chunk-ROUSHGC2.js";
import {
pathToHtmlFilename
} from "./chunk-4HD4NGA3.js";
import {
MagicString
} from "./chunk-Y4KSH55M.js";
import {
APP_COMPONENT_PATH,
APP_CONFIG_REQUEST_PATH,
APP_PATH,
DEBUG_COMPONENT_PATH,
NOT_FOUND_COMPONENT_PATH,
NOT_FOUND_REQUEST_PATH,
USER_APP_REQUEST_PATH,
USER_SITE_REQUEST_PATH
} from "./chunk-TAVOVDVB.js";
import {
ILES_APP_ENTRY
} from "./chunk-UPLBUAEH.js";
// src/node/plugin/middleware.ts
import { existsSync } from "fs";
import pc from "picocolors";
import { resolve as resolve2, relative as relative2, extname } from "pathe";
import createDebugger from "debug";
// src/node/server.ts
import { createServer as createViteServer, mergeConfig } from "vite";
// src/node/plugin/plugin.ts
import { promises as fs } from "fs";
import { basename, resolve, relative } from "pathe";
import { transformWithEsbuild } from "vite";
function isMarkdown(path) {
return path.endsWith(".mdx") || path.endsWith(".md");
}
function isSFCMain(path, query) {
return path.endsWith(".vue") && query.vue === void 0;
}
function isVueScript(path, query) {
return path.endsWith(".vue") && (!query.type || query.type === "script");
}
async function transformUserFile(path) {
return await exists(path) ? await transformWithEsbuild(await fs.readFile(path, "utf-8"), path, { sourcemap: false }) : { code: "export default {}" };
}
var templateLayoutRegex = /<template.*?\slayout=\s*['"](\w+)['"].*?>/;
function IslandsPlugins(appConfig) {
debug.config(appConfig);
let base;
let root;
let isBuild;
let server;
const appPath = resolve(appConfig.srcDir, "app.ts");
const sitePath = resolve(appConfig.srcDir, "site.ts");
const layoutsRoot = `/${relative(appConfig.root, appConfig.layoutsDir)}`;
const defaultLayoutPath = `${layoutsRoot}/default.vue`;
const plugins = appConfig.namedPlugins;
function isLayout(path) {
return path.includes(appConfig.layoutsDir);
}
return [
{
name: "iles",
enforce: "pre",
async configResolved(config) {
if (base) return;
base = config.base;
root = config.root;
isBuild = config.command === "build";
appConfig.resolvePath = config.createResolver();
writeComposablesDTS(root);
const result = await transformUserFile(appPath);
detectMDXComponents(result.code, appConfig, void 0);
},
async resolveId(id) {
if (id === ILES_APP_ENTRY)
return APP_PATH;
if (id === APP_CONFIG_REQUEST_PATH || id === USER_APP_REQUEST_PATH || id === USER_SITE_REQUEST_PATH)
return id;
if (id === NOT_FOUND_REQUEST_PATH)
return NOT_FOUND_COMPONENT_PATH;
if (id === defaultLayoutPath) return resolve(root, id.slice(1));
},
async load(id) {
if (id === APP_CONFIG_REQUEST_PATH) {
const { base: base2, debug: debug3, jsx, ssg: { sitemap }, siteUrl, markdown: { overrideElements = [] } } = appConfig;
const clientConfig = { base: base2, debug: debug3, root, jsx, sitemap, siteUrl, overrideElements };
return `export default ${default2(clientConfig)}`;
}
const userFilename = id === USER_APP_REQUEST_PATH && appPath || id === USER_SITE_REQUEST_PATH && sitePath;
if (userFilename) {
this.addWatchFile(userFilename);
const result = await transformUserFile(userFilename);
if (id === USER_APP_REQUEST_PATH)
detectMDXComponents(result.code, appConfig, server);
if (id === USER_SITE_REQUEST_PATH)
return extendSite(result.code, appConfig);
return result;
}
if ((isBuild || process.env.VITEST) && id.includes(defaultLayoutPath) && !await exists(resolve(root, defaultLayoutPath.slice(1))))
return "<template><slot/></template>";
},
transform(code, id) {
if (id === APP_COMPONENT_PATH && !isBuild && appConfig.debug)
return code.replace("const DebugPanel = () => null", () => `import DebugPanel from '${DEBUG_COMPONENT_PATH}'`);
},
handleHotUpdate({ file, server: server2 }) {
if (file === appPath) return [server2.moduleGraph.getModuleById(USER_APP_REQUEST_PATH)];
if (file === sitePath) return [server2.moduleGraph.getModuleById(USER_SITE_REQUEST_PATH)];
},
configureServer(devServer) {
server = devServer;
return configureMiddleware(appConfig, server, defaultLayoutPath);
}
},
{
name: "iles:detect-islands-in-vue",
enforce: "pre",
async transform(code, id) {
const { path, query } = parseId(id);
if (query.vue !== void 0 && query.type === "script-client")
return "export default {}; if (import.meta.hot) import.meta.hot.accept()";
if (isSFCMain(path, query) && code.includes("client:") && code.includes("<template"))
return wrapIslandsInSFC(appConfig, code, path);
}
},
{
name: "iles:layouts",
enforce: "pre",
transform(code, id) {
const { path, query } = parseId(id);
if (!isSFCMain(path, query) || !isLayout(path)) return;
const layoutName = code.match(templateLayoutRegex)?.[1] || false;
if (String(layoutName) === "false") return;
return wrapLayout(code, path);
}
},
plugins.vue,
...appConfig.vitePlugins,
plugins.components,
documentsPlugin(appConfig),
{
name: "iles:composables",
enforce: "post",
async transform(code, id) {
if (!id.startsWith(appConfig.srcDir)) return;
const { path, query } = parseId(id);
if (isVueScript(path, query) || /\.[tj]sx?/.test(path))
return await autoImportComposables(code, id);
}
},
{
name: "iles:page-data",
enforce: "post",
async transform(code, id, options) {
const { path, query } = parseId(id);
const isMdx = isMarkdown(path);
if (!isMdx && !isVueScript(path, query)) return;
const isLayoutFile = isLayout(path);
const isPage = plugins.pages.api.isPage(path);
if (!isMdx && !isLayoutFile && !isPage) return;
const sfcIndex = indexOfVueComponentDefinition(code);
if (!sfcIndex || sfcIndex === -1)
return;
const s = new MagicString(code);
const appendToSfc = (key, value) => s.appendRight(sfcIndex, value ? `${key}:${value},` : `${key},`);
if (isLayoutFile) {
appendToSfc("name", `'${pascalCase(basename(path).replace(".vue", "Layout"))}'`);
return s.toString();
}
appendToSfc("inheritAttrs", default2(false));
const { meta, layout = "default", route: _r, ...frontmatter } = await plugins.pages.api.frontmatterForPageOrFile(path, code);
if (isMdx) {
const keys = Object.keys(frontmatter);
const bindings = Object.entries(frontmatter).map(([key, value]) => `${key} = ${default2(value)}`);
bindings.push(`meta = ${default2(meta)}`);
bindings.push(`frontmatter = { ${keys.length > 0 ? keys.join(", ") : ""} }`);
s.prepend(`const ${bindings.join(", ")};`);
appendToSfc("...meta, ...frontmatter, meta, frontmatter");
} else {
s.prepend(`const _meta = ${default2(meta)}, _frontmatter = ${default2(frontmatter)};`);
appendToSfc("..._meta, ..._frontmatter, meta: _meta, frontmatter: _frontmatter");
}
if (isPage) {
appendToSfc("layoutName", default2(layout));
appendToSfc("layoutFn", String(layout) === "false" ? "false" : `() => import('${layoutsRoot}/${layout}.vue').then(m => m.default)`);
}
return s.toString();
}
},
{
name: "iles:page-hmr",
apply: "serve",
enforce: "post",
// Force a refresh for all page computed properties.
async transform(code, id) {
const { path } = parseId(id);
if (isLayout(path) || plugins.pages.api.isPage(path)) {
return `${code}
import.meta.hot?.accept('/${relative(root, path)}', (...args) => __ILES_PAGE_UPDATE__(args))
`;
}
}
},
appConfig.jsx === "preact" && {
name: "iles:preact-jsx-config",
config() {
return { esbuild: { include: /\.(tsx?|jsx)$/ } };
}
}
];
}
function indexOfVueComponentDefinition(code) {
let sfcConstIndex = code.indexOf("const _sfc_main = ");
if (sfcConstIndex === -1)
sfcConstIndex = code.indexOf("export default ");
if (sfcConstIndex === -1)
return;
const braceIndex = code.indexOf("{", sfcConstIndex);
if (braceIndex === -1)
return;
return braceIndex + 1;
}
// src/node/server.ts
async function createServer(root = process.cwd(), serverOptions = {}) {
const config = await resolveConfig(root);
const viteConfig = mergeConfig(config.vite, {
plugins: IslandsPlugins(config),
server: serverOptions
});
return {
config,
viteConfig,
server: await createViteServer(viteConfig)
};
}
// src/node/plugin/middleware.ts
var supportedExtensions = /* @__PURE__ */ new Set([".html", ".xml", ".json", ".rss", ".atom"]);
var debug2 = createDebugger("iles:html-page-fallback");
function configureMiddleware(config, server, defaultLayoutPath) {
restartOnConfigChanges(config, server);
const htmlPagesMiddleware = function ilesHtmlPagesMiddleware(req, res, next) {
let { url = "" } = req;
url = pathToHtmlFilename(url);
if (url.endsWith(".html")) {
const filename = resolve2(config.pagesDir, url.slice(1));
if (existsSync(filename)) {
url = `/${relative2(config.root, filename)}`;
debug2("Rewriting", req.method, req.url, "to", url);
req.url = url;
}
}
next();
};
server.middlewares.use(htmlPagesMiddleware);
return () => {
server.middlewares.use(async (req, res, next) => {
const url = req.url || "";
if (url.startsWith("/@fs/")) return next();
const filename = resolve2(config.root, url.slice(1));
if (await exists(filename)) return next();
if (url.includes(defaultLayoutPath)) {
res.statusCode = 200;
res.setHeader("content-type", "text/javascript");
res.end("export default false");
} else if (supportedExtensions.has(extname(url))) {
res.statusCode = 200;
res.setHeader("content-type", "text/html");
let html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<script type="module" src="${ILES_APP_ENTRY}"></script>
</body>
</html>`;
html = await server.transformIndexHtml(url, html, req.originalUrl);
res.end(html);
} else {
next();
}
});
};
}
async function restartOnConfigChanges(config, server) {
const restartIfConfigChanged = async (path) => {
if (path === config.configPath) {
server.config.logger.info(
pc.green(
`${relative2(process.cwd(), config.configPath)} changed, restarting server...`
),
{ clear: true, timestamp: true }
);
await server.close();
global.__vite_start_time = Date.now();
const { server: newServer } = await createServer(server.config.root, server.config.server);
await newServer.listen();
}
};
server.watcher.add(config.configPath);
server.watcher.on("add", restartIfConfigChanged);
server.watcher.on("change", restartIfConfigChanged);
}
export {
configureMiddleware,
IslandsPlugins,
createServer
};