UNPKG

12.9 kBJavaScriptView Raw
1var __rest = (this && this.__rest) || function (s, e) {
2 var t = {};
3 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4 t[p] = s[p];
5 if (s != null && typeof Object.getOwnPropertySymbols === "function")
6 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7 if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8 t[p[i]] = s[p[i]];
9 }
10 return t;
11};
12import { startServer } from "snowpack";
13import arg from "arg";
14import { join, resolve, extname } from "path";
15import { green, dim } from "kleur/colors";
16import polka from "polka";
17import { openInBrowser } from "../utils/open.js";
18import { readDir } from "../utils/fs.js";
19import { promises as fsp } from "fs";
20import { loadConfiguration } from "../utils/command.js";
21import { h } from "preact";
22import { generateStaticPropsContext } from "../utils/router.js";
23const noop = () => Promise.resolve();
24let devServer;
25let runtime;
26let renderToString;
27let csrSrc;
28let Document;
29let __HeadContext;
30let __InternalDocContext;
31let ErrorPage;
32let errorSrc;
33const loadErrorPage = async () => {
34 if (!ErrorPage) {
35 try {
36 const { exports: { default: UserErrorPage } } = await runtime.importModule("/src/pages/_error.js");
37 ErrorPage = UserErrorPage;
38 errorSrc = "/src/pages/_error.js";
39 }
40 catch (e) {
41 errorSrc = await devServer.getUrlForPackage('microsite/error');
42 const { exports: { default: InternalErrorPage }, } = await runtime.importModule(errorSrc);
43 ErrorPage = InternalErrorPage;
44 }
45 }
46 return [ErrorPage, errorSrc];
47};
48const renderPage = async (page, initialProps) => {
49 var _a, _b, _c, _d;
50 if (!renderToString) {
51 const preactRenderToStringSrc = await devServer.getUrlForPackage('preact-render-to-string');
52 renderToString = await runtime
53 .importModule(preactRenderToStringSrc)
54 .then(({ exports: { default: mod } }) => mod);
55 }
56 if (!Document) {
57 const [documentSrc, csrUrl] = await Promise.all([devServer.getUrlForPackage('microsite/document'), devServer.getUrlForPackage('microsite/client/csr')]);
58 csrSrc = csrUrl;
59 const { exports: { Document: InternalDocument, __HeadContext: __Head, __InternalDocContext: __Doc }, } = await runtime.importModule(documentSrc);
60 __HeadContext = __Head;
61 __InternalDocContext = __Doc;
62 try {
63 const { exports: { default: UserDocument }, } = await runtime.importModule("/src/pages/_document.js");
64 Document = UserDocument;
65 }
66 catch (e) {
67 Document = InternalDocument;
68 }
69 }
70 try {
71 let pathname = page.replace("/src/pages/", "");
72 let Component = null;
73 let getStaticProps = noop;
74 let getStaticPaths = noop;
75 let pageProps = initialProps !== null && initialProps !== void 0 ? initialProps : {};
76 let paths = [];
77 try {
78 let { exports: { default: Page } } = await runtime.importModule(page);
79 if (typeof Page === "function")
80 Component = Page;
81 if (Page.Component) {
82 Component = Page.Component;
83 getStaticProps = (_a = Page.getStaticProps) !== null && _a !== void 0 ? _a : noop;
84 getStaticPaths = (_b = Page.getStaticPaths) !== null && _b !== void 0 ? _b : noop;
85 }
86 }
87 catch (e) {
88 const [Page, errorSrc] = await loadErrorPage();
89 Component = ErrorPage;
90 pageProps = (initialProps === null || initialProps === void 0 ? void 0 : initialProps.statusCode) ? initialProps : { statusCode: 404 };
91 page = errorSrc;
92 pathname = "/_error";
93 if (typeof Page === "function")
94 Component = Page;
95 if (Page.Component) {
96 Component = Page.Component;
97 getStaticProps = (_c = Page.getStaticProps) !== null && _c !== void 0 ? _c : noop;
98 getStaticPaths = (_d = Page.getStaticPaths) !== null && _d !== void 0 ? _d : noop;
99 }
100 }
101 paths = await getStaticPaths({}).then((res) => res && res.paths);
102 paths =
103 paths &&
104 paths.map((pathOrParams) => generateStaticPropsContext(pathname, pathOrParams));
105 const match = paths &&
106 paths.find((ctx) => ctx.path === pathname || ctx.path === `${pathname}/index`);
107 if (paths && !match) {
108 const [ErrorPage, errorSrc] = await loadErrorPage();
109 Component = ErrorPage;
110 pageProps = { statusCode: 404 };
111 page = errorSrc;
112 }
113 else {
114 let ctx = paths ? match : generateStaticPropsContext(pathname, pathname);
115 pageProps = await getStaticProps(ctx).then((res) => res && res.props);
116 if (!pageProps)
117 pageProps = initialProps;
118 }
119 const headContext = {
120 head: {
121 current: [],
122 },
123 };
124 const HeadProvider = ({ children }) => {
125 return h(__HeadContext.Provider, Object.assign({ value: headContext }, { children }));
126 };
127 const _e = await Document.prepare({
128 renderPage: async () => ({
129 __renderPageResult: renderToString(h(HeadProvider, null,
130 h(Component, Object.assign({}, pageProps)))),
131 }),
132 }), { __renderPageResult } = _e, docProps = __rest(_e, ["__renderPageResult"]);
133 const docContext = {
134 dev: page,
135 devProps: pageProps !== null && pageProps !== void 0 ? pageProps : {},
136 __csrUrl: csrSrc,
137 __renderPageHead: headContext.head.current,
138 __renderPageResult,
139 };
140 let contents = renderToString(h(__InternalDocContext.Provider, { value: docContext, children: h(Document, Object.assign({}, docProps)) }));
141 return `<!DOCTYPE html>\n<!-- Generated by microsite -->\n${contents}`;
142 }
143 catch (e) {
144 console.error(e);
145 return;
146 }
147};
148const EXTS = [".js", ".jsx", ".ts", ".tsx", ".mjs"];
149function parseArgs(argv) {
150 return arg({
151 "--port": Number,
152 "--no-open": Boolean,
153 // Aliases
154 "-p": "--port",
155 }, { permissive: true, argv });
156}
157export default async function dev(argvOrParsedArgs) {
158 var _a;
159 const cwd = process.cwd();
160 const args = Array.isArray(argvOrParsedArgs)
161 ? parseArgs(argvOrParsedArgs)
162 : argvOrParsedArgs;
163 let PORT = (_a = args["--port"]) !== null && _a !== void 0 ? _a : 8888;
164 const config = await loadConfiguration("dev");
165 const snowpack = await startServer({
166 config,
167 lockfile: null,
168 });
169 devServer = snowpack;
170 runtime = snowpack.getServerRuntime();
171 snowpack.onFileChange(({ filePath }) => {
172 const url = snowpack.getUrlForFile(filePath);
173 if (url === '/src/pages/_document.js') {
174 Document = null;
175 }
176 if (url === '/src/pages/_error.js') {
177 ErrorPage = null;
178 }
179 });
180 const sendErr = async (res, props) => {
181 var _a;
182 const contents = await renderPage(`/_error`, props);
183 res.writeHead((_a = props === null || props === void 0 ? void 0 : props.statusCode) !== null && _a !== void 0 ? _a : 500, {
184 "Content-Type": "text/html",
185 });
186 res.end(contents);
187 };
188 const server = polka()
189 .use(async (req, res, next) => {
190 var _a;
191 if ((_a = req.url) === null || _a === void 0 ? void 0 : _a.endsWith(".js")) {
192 res.setHeader("Content-Type", "application/javascript");
193 }
194 next();
195 })
196 .use(async (req, res, next) => {
197 var _a, _b;
198 if (req.url === "/")
199 return next();
200 const clean = /(\.html|index\.html|index|\/)$/;
201 if (clean.test((_a = req.url) !== null && _a !== void 0 ? _a : "")) {
202 res.writeHead(302, {
203 Location: (_b = req.url) === null || _b === void 0 ? void 0 : _b.replace(clean, ""),
204 });
205 res.end();
206 }
207 next();
208 })
209 .use(async (req, res, next) => {
210 if (req.url !== "/" &&
211 !(req.url.endsWith(".html") || req.url.indexOf(".") === -1))
212 return next();
213 let base = req.url.slice(1);
214 if (base.endsWith(".html"))
215 base = base.slice(0, ".html".length * -1);
216 if (base === "")
217 base = "index";
218 const loadAndSSR = async (base) => {
219 try {
220 const url = `/src/pages/${base}.js`;
221 const result = await snowpack.loadUrl(url, { isSSR: true });
222 if (!result)
223 throw new Error();
224 res.setHeader("Content-Type", "text/html");
225 res.end(await renderPage(url));
226 return true;
227 }
228 catch (err) { }
229 return false;
230 };
231 const findPotentialMatch = async (base) => {
232 const baseParts = [...base.split("/"), "index"];
233 const pages = join(cwd, "src", "pages");
234 let files = await readDir(pages);
235 files = files
236 .filter((file) => EXTS.includes(extname(file)))
237 .map((file) => file.slice(pages.length, extname(file).length * -1))
238 .filter((file) => {
239 if (file.indexOf("[") === -1)
240 return false;
241 const parts = file.slice(1).split("/");
242 if (parts.length === baseParts.length - 1)
243 return parts.every((part, i) => part.indexOf("[") > -1 ? true : part === baseParts[i]);
244 if (parts.length === baseParts.length)
245 return parts.every((part, i) => part.indexOf("[") > -1 ? true : part === baseParts[i]);
246 if (file.indexOf("[[") > -1)
247 return parts.every((part, i) => {
248 if (part.indexOf("[["))
249 return i === parts.length - 1;
250 if (part.indexOf("["))
251 return true;
252 return part === baseParts[i];
253 });
254 });
255 if (files.length === 0)
256 return null;
257 if (files.length === 1)
258 return files[0].slice(1);
259 if (files.length > 1) {
260 // TODO: rank direct matches above catch-all routes
261 // console.log(files);
262 return files[0];
263 }
264 };
265 const direct = await loadAndSSR(base);
266 if (direct) {
267 return next();
268 }
269 const index = await loadAndSSR(`${base}/index`);
270 if (index) {
271 return next();
272 }
273 const dynamic = await findPotentialMatch(base);
274 if (dynamic) {
275 await loadAndSSR(dynamic);
276 }
277 next();
278 })
279 .use(async (req, res, next) => {
280 try {
281 // Respond directly if asset is found
282 const result = await snowpack.loadUrl(req.url);
283 if (result.contentType)
284 res.setHeader("Content-Type", result.contentType);
285 const MIME_EXCLUDE = ["image", "font"];
286 if (req.url.indexOf("/_snowpack/pkg/microsite") === -1 &&
287 result.contentType &&
288 !MIME_EXCLUDE.includes(result.contentType.split("/")[0])) {
289 result.contents = result.contents
290 .toString()
291 .replace(/preact\/hooks/, "microsite/client/hooks");
292 }
293 return res.end(result.contents);
294 }
295 catch (err) { }
296 next();
297 })
298 .use(async (req, res, next) => {
299 try {
300 let localPath = resolve(cwd, `.${req.url}`);
301 const stats = await fsp.stat(localPath);
302 if (stats.isDirectory()) {
303 let contents = await readDir(localPath);
304 contents = contents.map((path) => path.slice(localPath.length));
305 res.setHeader("Content-Type", "application/json");
306 return res.end(JSON.stringify(contents));
307 }
308 }
309 catch (err) { }
310 next();
311 })
312 .get("*", (_req, res) => sendErr(res, { statusCode: 404 }));
313 await new Promise((resolve) => server.listen(PORT, (err) => {
314 if (err)
315 throw err;
316 resolve();
317 }));
318 let protocol = "http:";
319 let hostname = "localhost";
320 if (!args["--no-open"]) {
321 await openInBrowser(protocol, hostname, PORT, "/", "chrome");
322 }
323 console.log(`${dim("[microsite]")} ${green("✔")} Microsite started on ${green(`${protocol}//${hostname}:${PORT}`)}\n`);
324}