UNPKG

microsite

Version:
336 lines (335 loc) 13.6 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { startServer } from "snowpack"; import arg from "arg"; import { join, resolve, extname } from "path"; import { green, dim } from "kleur/colors"; import polka from "polka"; import { openInBrowser } from "../utils/open.js"; import { readDir } from "../utils/fs.js"; import { promises as fsp } from "fs"; import { loadConfiguration } from "../utils/command.js"; import { h } from "preact"; import { generateStaticPropsContext, normalizePathName, } from "../utils/router.js"; const noop = () => Promise.resolve(); let devServer; let runtime; let renderToString; let csrSrc; let Document; let __HeadContext; let __InternalDocContext; let ErrorPage; let errorSrc; const loadErrorPage = async () => { if (!ErrorPage) { try { const { exports: { default: UserErrorPage }, } = await runtime.importModule("/src/pages/_error.js"); ErrorPage = UserErrorPage; errorSrc = "/src/pages/_error.js"; } catch (e) { errorSrc = await devServer.getUrlForPackage("microsite/error"); const { exports: { default: InternalErrorPage }, } = await runtime.importModule(errorSrc); ErrorPage = InternalErrorPage; } } return [ErrorPage, errorSrc]; }; const renderPage = async (componentPath, absoluteUrl, initialProps) => { var _a, _b, _c, _d; if (!renderToString) { const preactRenderToStringSrc = await devServer.getUrlForPackage("preact-render-to-string"); renderToString = await runtime .importModule(preactRenderToStringSrc) .then(({ exports: { default: mod } }) => mod); } if (!Document) { const [documentSrc, csrUrl] = await Promise.all([ devServer.getUrlForPackage("microsite/document"), devServer.getUrlForPackage("microsite/client/csr"), ]); csrSrc = csrUrl; const { exports: { Document: InternalDocument, __HeadContext: __Head, __InternalDocContext: __Doc, }, } = await runtime.importModule(documentSrc); __HeadContext = __Head; __InternalDocContext = __Doc; try { const { exports: { default: UserDocument }, } = await runtime.importModule("/src/pages/_document.js"); Document = UserDocument; } catch (e) { Document = InternalDocument; } } try { let pathname = componentPath.replace("/src/pages/", ""); let Component = null; let getStaticProps = noop; let getStaticPaths = noop; let pageProps = initialProps !== null && initialProps !== void 0 ? initialProps : {}; let paths = []; try { let { exports: { default: Page }, } = await runtime.importModule(componentPath); if (typeof Page === "function") Component = Page; if (Page.Component) { Component = Page.Component; getStaticProps = (_a = Page.getStaticProps) !== null && _a !== void 0 ? _a : noop; getStaticPaths = (_b = Page.getStaticPaths) !== null && _b !== void 0 ? _b : noop; } } catch (e) { const [Page, errorSrc] = await loadErrorPage(); Component = ErrorPage; pageProps = (initialProps === null || initialProps === void 0 ? void 0 : initialProps.statusCode) ? initialProps : { statusCode: 404 }; componentPath = errorSrc; pathname = "/_error"; if (typeof Page === "function") Component = Page; if (Page.Component) { Component = Page.Component; getStaticProps = (_c = Page.getStaticProps) !== null && _c !== void 0 ? _c : noop; getStaticPaths = (_d = Page.getStaticPaths) !== null && _d !== void 0 ? _d : noop; } } paths = await getStaticPaths({}).then((res) => res && res.paths); paths = paths && paths.map((pathOrParams) => generateStaticPropsContext(pathname, pathOrParams)); const match = paths && paths.find((ctx) => ctx.path === pathname || ctx.path === `${pathname}/index` || ctx.path === normalizePathName(absoluteUrl)); if (paths && !match) { const [ErrorPage, errorSrc] = await loadErrorPage(); Component = ErrorPage; pageProps = { statusCode: 404 }; componentPath = errorSrc; } else { let ctx = paths ? match : generateStaticPropsContext(pathname, pathname); pageProps = await getStaticProps(ctx).then((res) => res && res.props); if (!pageProps) pageProps = initialProps; } const headContext = { head: { current: [], }, }; const HeadProvider = ({ children }) => { return h(__HeadContext.Provider, Object.assign({ value: headContext }, { children })); }; const _e = await Document.prepare({ renderPage: async () => ({ __renderPageResult: renderToString(h(HeadProvider, null, h(Component, Object.assign({}, pageProps)))), }), }), { __renderPageResult } = _e, docProps = __rest(_e, ["__renderPageResult"]); const docContext = { dev: componentPath, devProps: pageProps !== null && pageProps !== void 0 ? pageProps : {}, __csrUrl: csrSrc, __renderPageHead: headContext.head.current, __renderPageResult, }; let contents = renderToString(h(__InternalDocContext.Provider, { value: docContext, children: h(Document, Object.assign({}, docProps)) })); return `<!DOCTYPE html>\n<!-- Generated by microsite -->\n${contents}`; } catch (e) { console.error(e); return; } }; const EXTS = [".js", ".jsx", ".ts", ".tsx", ".mjs"]; function parseArgs(argv) { return arg({ "--port": Number, "--no-open": Boolean, // Aliases "-p": "--port", }, { permissive: true, argv }); } export default async function dev(argvOrParsedArgs) { var _a; const cwd = process.cwd(); const args = Array.isArray(argvOrParsedArgs) ? parseArgs(argvOrParsedArgs) : argvOrParsedArgs; let PORT = (_a = args["--port"]) !== null && _a !== void 0 ? _a : 8888; const config = await loadConfiguration("dev"); const snowpack = await startServer({ config, lockfile: null, }); devServer = snowpack; runtime = snowpack.getServerRuntime(); snowpack.onFileChange(({ filePath }) => { const url = snowpack.getUrlForFile(filePath); if (url === "/src/pages/_document.js") { Document = null; } if (url === "/src/pages/_error.js") { ErrorPage = null; } }); const sendErr = async (res, props) => { var _a; // Calling `renderPage` with a component and path that do not exist // triggers rendering of an error page. const contents = await renderPage(`/_error`, `/_error`, props); res.writeHead((_a = props === null || props === void 0 ? void 0 : props.statusCode) !== null && _a !== void 0 ? _a : 500, { "Content-Type": "text/html", }); res.end(contents); }; const server = polka() .use(async (req, res, next) => { var _a; if ((_a = req.url) === null || _a === void 0 ? void 0 : _a.endsWith(".js")) { res.setHeader("Content-Type", "application/javascript"); } next(); }) .use(async (req, res, next) => { var _a, _b; if (req.url === "/") return next(); const clean = /(\.html|index\.html|index|\/)$/; if (clean.test((_a = req.url) !== null && _a !== void 0 ? _a : "")) { res.writeHead(302, { Location: (_b = req.url) === null || _b === void 0 ? void 0 : _b.replace(clean, ""), }); res.end(); } next(); }) .use(async (req, res, next) => { if (req.url !== "/" && !(req.url.endsWith(".html") || req.url.indexOf(".") === -1)) return next(); let base = req.url.slice(1); if (base.endsWith(".html")) base = base.slice(0, ".html".length * -1); if (base === "") base = "index"; const findPageComponentPathForBaseUrl = async (base) => { const possiblePagePaths = [base, `${base}/index`].map(buildPageComponentPathForBaseUrl); for (const pagePath of possiblePagePaths) { if (await isPageComponentPresent(pagePath)) { return pagePath; } } const dynamicBaseUrl = await findPotentialMatch(base); if (!dynamicBaseUrl) { return null; } return buildPageComponentPathForBaseUrl(dynamicBaseUrl); }; const buildPageComponentPathForBaseUrl = (base) => `/src/pages/${base}.js`; const isPageComponentPresent = async (path) => { try { await snowpack.loadUrl(path, { isSSR: true }); return true; } catch (_a) { return false; } }; const findPotentialMatch = async (base) => { const baseParts = [...base.split("/"), "index"]; const pages = join(cwd, "src", "pages"); let files = await readDir(pages); files = files .filter((file) => EXTS.includes(extname(file))) .map((file) => file.slice(pages.length, extname(file).length * -1)) .filter((file) => { if (file.indexOf("[") === -1) return false; const parts = file.slice(1).split("/"); if (parts.length === baseParts.length - 1) return parts.every((part, i) => part.indexOf("[") > -1 ? true : part === baseParts[i]); if (parts.length === baseParts.length) return parts.every((part, i) => part.indexOf("[") > -1 ? true : part === baseParts[i]); if (file.indexOf("[[") > -1) return parts.every((part, i) => { if (part.indexOf("[[")) return i === parts.length - 1; if (part.indexOf("[")) return true; return part === baseParts[i]; }); }); if (files.length === 0) return null; if (files.length === 1) return files[0].slice(1); if (files.length > 1) { // TODO: rank direct matches above catch-all routes // console.log(files); return files[0]; } }; const pagePath = await findPageComponentPathForBaseUrl(base); if (!pagePath) { return next(); } const absoluteUrl = `/${base}`; res.setHeader("Content-Type", "text/html"); res.end(await renderPage(pagePath, absoluteUrl)); }) .use(async (req, res, next) => { try { // Respond directly if asset is found const result = await snowpack.loadUrl(req.url); if (result.contentType) res.setHeader("Content-Type", result.contentType); const MIME_EXCLUDE = ["image", "font"]; if (req.url.indexOf("/_snowpack/pkg/microsite") === -1 && result.contentType && !MIME_EXCLUDE.includes(result.contentType.split("/")[0])) { result.contents = result.contents .toString() .replace(/preact\/hooks/, "microsite/client/hooks"); } return res.end(result.contents); } catch (err) { } next(); }) .use(async (req, res, next) => { try { let localPath = resolve(cwd, `.${req.url}`); const stats = await fsp.stat(localPath); if (stats.isDirectory()) { let contents = await readDir(localPath); contents = contents.map((path) => path.slice(localPath.length)); res.setHeader("Content-Type", "application/json"); return res.end(JSON.stringify(contents)); } } catch (err) { } next(); }) .get("*", (_req, res) => sendErr(res, { statusCode: 404 })); await new Promise((resolve) => server.listen(PORT, (err) => { if (err) throw err; resolve(); })); let protocol = "http:"; let hostname = "localhost"; if (!args["--no-open"]) { await openInBrowser(protocol, hostname, PORT, "/", "chrome"); } console.log(`${dim("[microsite]")} ${green("✔")} Microsite started on ${green(`${protocol}//${hostname}:${PORT}`)}\n`); }