microsite
Version:
<br /> <br />
336 lines (335 loc) • 13.6 kB
JavaScript
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`);
}