UNPKG

9.21 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 crypto from "crypto";
13import { resolve, join } from "path";
14import { writeFile, copyFile, fileExists } from "./fs.js";
15import module from "module";
16const { createRequire } = module;
17const require = createRequire(import.meta.url);
18import { Document as InternalDocument, __HeadContext, __InternalDocContext } from "../document.js";
19import { h } from "preact";
20import { renderToString } from "preact-render-to-string";
21import { generateStaticPropsContext } from "./router.js";
22import fetch from 'node-fetch';
23// import { createPrefetch, getCacheEntry, getPreviousKey } from "./prefetch.js";
24export const CACHE_DIR = ".microsite/cache";
25export const STAGING_DIR = ".microsite/staging";
26export const SSR_DIR = ".microsite/ssr";
27export const OUT_DIR_NO_BASE = "./dist";
28export let OUT_DIR = "./dist";
29export const setBasePath = (p) => OUT_DIR = p === '/' ? OUT_DIR : join(OUT_DIR, ...p.replace(/^\//, '').replace(/\/$/, '').split('/'));
30export const getFileNameFromPath = (path) => {
31 return path
32 .split(STAGING_DIR)[1]
33 .replace(/^\/src\//, "")
34 .replace(/([\[\]])/gi, "_")
35 .replace(/\..*$/, "");
36};
37const PROXY_IMPORT_REGEX = /^.*\.(\w+)\.proxy\.js$/g;
38export const proxyImportTransformer = {
39 filter: (source) => PROXY_IMPORT_REGEX.test(source),
40 transform: (source) => source.replace(PROXY_IMPORT_REGEX, (fullMatch, originalExt) => {
41 if (originalExt === "json") {
42 return fullMatch;
43 }
44 return fullMatch.replace(".proxy.js", "");
45 }),
46};
47const PREACT_IMPORT_REGEX = /^.*_snowpack\/pkg\/preact([\/\w]+)?\.js$/gm;
48export const preactImportTransformer = {
49 filter: (source) => PREACT_IMPORT_REGEX.test(source),
50 transform: (source) => source.replace(PREACT_IMPORT_REGEX, (_match, subpath) => {
51 return subpath ? `preact${subpath}` : `preact`;
52 }),
53};
54const PREACT_VERSION = require("preact/package.json").version;
55const PREACT_CDN_LOOKUP = {
56 preact: `https://cdn.skypack.dev/preact@${PREACT_VERSION}`,
57 "preact/hooks": `https://cdn.skypack.dev/preact@${PREACT_VERSION}/hooks`,
58};
59let PREACT_CDN_SOURCES = null;
60const resolvePreactCdnSources = async () => {
61 if (PREACT_CDN_SOURCES)
62 return;
63 const mdls = Object.keys(PREACT_CDN_LOOKUP);
64 const pinnedUrls = await Promise.all(mdls.map(mdl => {
65 const lookupUrl = PREACT_CDN_LOOKUP[mdl];
66 return fetch(lookupUrl).then(res => `https://cdn.skypack.dev${res.headers.get('x-pinned-url')}`);
67 }));
68 PREACT_CDN_SOURCES = mdls.reduce((acc, curr, i) => {
69 return Object.assign(Object.assign({}, acc), { [curr]: pinnedUrls[i] });
70 }, {});
71 return;
72};
73const PREACT_REGEX = /['"]preact([\/\w]+)?['"]/gm;
74export const preactToCDN = async (code) => {
75 if (!/preact/gm.test(code)) {
76 return code;
77 }
78 await resolvePreactCdnSources();
79 return code.replace(PREACT_REGEX, (fullMatch, subpath) => {
80 if (subpath === "/hooks") {
81 return fullMatch.replace("preact/hooks", PREACT_CDN_SOURCES["preact/hooks"]);
82 }
83 return fullMatch.replace("preact", PREACT_CDN_SOURCES["preact"]);
84 });
85};
86const WITH_HYDRATE_REGEX = /import\s+\{\s*withHydrate\s*(?:as (\w+))?\}\s+from\s+['"]microsite\/hydrate(?:\.js)?['"];?/gim;
87export const withHydrateTransformer = {
88 filter: (source) => WITH_HYDRATE_REGEX.test(source),
89 transform: (source) => source.replace(WITH_HYDRATE_REGEX, (_match, name = "withHydrate") => {
90 return `const ${name} = i=>i;`;
91 }),
92};
93/**
94 * For the final browser code, we need to strip out withHydrate
95 * by replacing it with an identity function which can be
96 * completely stripped by a minifier
97 */
98export const stripWithHydrate = (source) => {
99 if (!withHydrateTransformer.filter(source))
100 return source;
101 return withHydrateTransformer.transform(source);
102};
103export const hashContentSync = (content, len) => {
104 const hash = crypto.createHash("sha256");
105 hash.update(Buffer.from(content));
106 let res = hash.digest("hex");
107 if (typeof len === "number")
108 res = res.slice(0, len);
109 return res;
110};
111export const emitFile = (filename, content) => writeFile(resolve(process.cwd(), join(SSR_DIR, filename)), content.toString());
112export const emitFinalAsset = (filename, content) => writeFile(resolve(process.cwd(), join(OUT_DIR, filename)), content.toString());
113export const copyAssetToFinal = async (path, transform) => copyFile(path, resolve(process.cwd(), join(OUT_DIR, path.slice(resolve(SSR_DIR).length))), { transform });
114const importPage = (filename) => import(resolve(process.cwd(), join(SSR_DIR, filename))).then((mod) => mod.default);
115let UserDocument = null;
116const getDocument = async () => {
117 if (UserDocument)
118 return UserDocument;
119 else if (UserDocument === false)
120 return InternalDocument;
121 else if (await fileExists(resolve(process.cwd(), join(SSR_DIR, 'pages', '_document.js')))) {
122 UserDocument = await importPage(join('pages', '_document.js'));
123 return UserDocument;
124 }
125 UserDocument = false;
126 return InternalDocument;
127};
128export const renderPage = async (data, manifest, { basePath = '/', debug = false, hasGlobalScript = false } = {}) => {
129 let [Document, Page] = await Promise.all([getDocument(), importPage(manifest.name), resolvePreactCdnSources()]);
130 Page = unwrapPage(Page);
131 const pageProps = data.props;
132 const headContext = {
133 head: {
134 current: [],
135 },
136 };
137 const HeadProvider = ({ children }) => {
138 return (h(__HeadContext.Provider, { value: headContext }, children));
139 };
140 const _a = await Document.prepare({
141 renderPage: async () => ({
142 __renderPageResult: renderToString(h(HeadProvider, null,
143 h(Page, Object.assign({}, pageProps))))
144 })
145 }), { __renderPageResult } = _a, docProps = __rest(_a, ["__renderPageResult"]);
146 const docContext = {
147 dev: false,
148 manifest,
149 styles: manifest.hydrateStyleBindings,
150 scripts: manifest.hydrateBindings,
151 preload: manifest.hydrateBindings
152 ? Object.values(PREACT_CDN_SOURCES)
153 : [],
154 preconnect: [],
155 debug,
156 hasGlobalScript,
157 basePath,
158 __renderPageResult,
159 __renderPageHead: headContext.head.current
160 };
161 let contents = renderToString(h(__InternalDocContext.Provider, { value: docContext },
162 h(Document, Object.assign({}, docProps))));
163 contents = contents.replace(/<hydrate-marker>(\?h[\s\S]*?\?)<\/hydrate-marker>/g, '<$1>\n');
164 contents = '<!DOCTYPE html>\n<!-- Generated by microsite -->\n' + contents;
165 return {
166 name: `${data.route.replace(/\.js$/, '')}.html`,
167 contents,
168 };
169};
170export const unwrapPage = (Page) => {
171 return Page.Component ? Page.Component : Page;
172};
173let noop = () => Promise.resolve();
174export const importDataMethods = (path) => import(path).then((mod) => {
175 var _a, _b;
176 const Page = mod.default;
177 let getStaticPaths = noop;
178 let getStaticProps = noop;
179 if (Page.Component) {
180 getStaticPaths = (_a = Page.getStaticPaths) !== null && _a !== void 0 ? _a : noop;
181 getStaticProps = (_b = Page.getStaticProps) !== null && _b !== void 0 ? _b : noop;
182 }
183 return { getStaticPaths, getStaticProps };
184});
185// const hashFn = (s: (...args: any[]) => any) => s.toString().split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0);
186export async function applyDataMethods(fileName, handlers) {
187 const { getStaticPaths, getStaticProps } = handlers;
188 // TODO: prefetch
189 let staticPaths = [];
190 staticPaths = await getStaticPaths({}).then((res) => { var _a; return (_a = res === null || res === void 0 ? void 0 : res.paths) !== null && _a !== void 0 ? _a : []; });
191 if (staticPaths.length === 0) {
192 const ctx = generateStaticPropsContext(fileName, fileName);
193 const staticProps = await getStaticProps(ctx).then((res) => { var _a; return (_a = res === null || res === void 0 ? void 0 : res.props) !== null && _a !== void 0 ? _a : {}; });
194 return [{ name: fileName, route: ctx.path, props: staticProps }];
195 }
196 return Promise.all(staticPaths.map((pathOrParams) => {
197 const ctx = generateStaticPropsContext(fileName, pathOrParams);
198 return getStaticProps(ctx).then((res) => {
199 var _a;
200 let staticProps = (_a = res === null || res === void 0 ? void 0 : res.props) !== null && _a !== void 0 ? _a : {};
201 return { name: fileName, route: ctx.path, props: staticProps };
202 });
203 }));
204}
205// export async function printManifest(manifest: ManifestEntry[]) {
206// let tree = [];
207// manifest.forEach(entry => {
208// entry.name
209// })
210// }