UNPKG

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