microsite
Version:
<br /> <br />
243 lines (240 loc) • 9.79 kB
JavaScript
import { startDevServer } 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";
const pageScript = (page, props) => `import { csr } from '/web_modules/microsite/client/csr.js';
import Page from '${page}';
csr("${page.replace(/\/src\/pages/, "").replace(/\.js$/, "")}", ${props ? `Page, ${JSON.stringify(props)}` : `Page`});`;
const errorScript = (props) => `import { h, render } from '/web_modules/preact.js';
import Page from '/web_modules/microsite/error.js';
const root = document.querySelector('#__microsite');
render(h(Page, ${JSON.stringify(props)}, null), root);`;
const globalScript = () => `(async () => {
try { await import('/src/global/index.css.proxy.js'); } catch (e) {}
try {
const global = await import('/src/global/index.js').then(mod => mod.default);
if (global) global();
} catch (e) {}
document.documentElement.style.removeProperty('visibility');
})()`;
const doc = (page, props) => `<!doctype html>\n<!-- Generated by microsite -->\n<html lang="en" dir="ltr" style="visibility:hidden">\n\t<head></head>\n\t<body>
\t\t<div id="__microsite"></div>
\t\t<script data-csr="true">window.HMR_WEBSOCKET_URL = 'ws://localhost:3333';</script>
\t\t<script type="module" src="/__snowpack__/hmr-client.js"></script>
\t\t<script type="module" data-microsite="page">${pageScript(page, props)}</script>
\t\t<script type="module" data-microsite="global">${globalScript()}</script>
</body>\n</html>`;
const errorPage = (props) => `<!doctype html>\n<!-- Generated by microsite -->\n<html lang="en" dir="ltr">\n\t<head></head>\n\t<body>
\t\t<div id="__microsite"></div>
\t\t<script data-csr="true">window.HMR_WEBSOCKET_URL = 'ws://localhost:3333';</script>
\t\t<script type="module" src="/__snowpack__/hmr-client.js"></script>
\t\t<script type="module" data-microsite="page">${errorScript(props)}</script>
\t\t<script type="module" data-microsite="global">${globalScript()}</script>`;
const EXTS = [".js", ".jsx", ".ts", ".tsx", ".mjs"];
function parseArgs(argv) {
return arg({
"--port": Number,
// Aliases
"-p": "--port",
}, { permissive: true, argv });
}
export default async function dev(argv) {
var _a;
const cwd = process.cwd();
const args = parseArgs(argv);
let PORT = (_a = args["--port"]) !== null && _a !== void 0 ? _a : 8888;
const [errs, config] = await loadConfiguration('dev');
if (errs) {
errs.forEach((err) => console.error(err));
return;
}
const snowpack = await startDevServer({
cwd: process.cwd(),
config,
lockfile: null,
});
const loadErrorPage = async (contentType, props) => {
try {
const url = `/src/pages/_error.js`;
const result = await snowpack.loadUrl(url);
if (!result)
throw new Error();
if (contentType === "text/html")
return doc("/src/pages/_error.js", props);
if (contentType === "application/javascript")
return result.contents.toString();
}
catch (e) { }
try {
const url = `/web_modules/microsite/error.js`;
const result = await snowpack.loadUrl(url);
if (!result)
throw new Error();
if (contentType === "text/html")
return errorPage(props);
if (contentType === "application/javascript")
return result.contents.toString();
}
catch (e) { }
return null;
};
const sendErr = async (res, props) => {
var _a;
const contents = await loadErrorPage("text/html", props);
res.writeHead((_a = 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) => {
var _a;
if (((_a = req.url) === null || _a === void 0 ? void 0 : _a.indexOf("microsite")) > -1) {
if (req.url.endsWith("_error.js")) {
const contents = await loadErrorPage("application/javascript");
res.setHeader("Content-Type", "application/javascript");
res.end(contents);
}
else {
req.url.replace("_microsite", "microsite");
}
}
next();
})
.use(async (req, res, next) => {
if (!(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 loadAndSSR = async (base) => {
try {
const url = `/src/pages/${base}.js`;
const result = await snowpack.loadUrl(url, { isSSR: true });
if (!result)
throw new Error();
res.setHeader("Content-Type", "text/html");
res.end(doc(url));
return true;
}
catch (err) {
// console.error(err);
}
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 direct = await loadAndSSR(base);
if (direct) {
return next();
}
const index = await loadAndSSR(`${base}/index`);
if (index) {
return next();
}
const dynamic = await findPotentialMatch(base);
if (dynamic) {
await loadAndSSR(dynamic);
}
next();
})
.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);
if (req.url.indexOf("/web_modules/microsite") === -1) {
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";
await openInBrowser(protocol, hostname, PORT, "chrome");
console.log(`${dim("[microsite]")} ${green("✔")} Microsite started on ${green(`${protocol}//${hostname}:${PORT}`)}\n`);
}