UNPKG

iles

Version:

Vite & Vue powered static site generator with partial hydration

344 lines (337 loc) 12 kB
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 };