|
1 | 1 | import crypto from 'node:crypto';
|
| 2 | +import path from 'node:path'; |
2 | 3 | import {
|
3 | 4 | ComponentChildren,
|
4 | 5 | ComponentType,
|
@@ -37,6 +38,32 @@ import {htmlPretty} from './html-pretty.js';
|
37 | 38 | import {getFallbackLocales} from './i18n-fallbacks.js';
|
38 | 39 | import {normalizeUrlPath, replaceParams, Router} from './router.js';
|
39 | 40 |
|
| 41 | +const CONTENT_TYPES: Record<string, string> = { |
| 42 | + 'html': 'text/html', |
| 43 | + 'htm': 'text/html', |
| 44 | + 'css': 'text/css', |
| 45 | + 'js': 'application/javascript', |
| 46 | + 'json': 'application/json', |
| 47 | + 'png': 'image/png', |
| 48 | + 'jpg': 'image/jpeg', |
| 49 | + 'jpeg': 'image/jpeg', |
| 50 | + 'gif': 'image/gif', |
| 51 | + 'svg': 'image/svg+xml', |
| 52 | + 'txt': 'text/plain', |
| 53 | + 'xml': 'application/xml', |
| 54 | + 'pdf': 'application/pdf', |
| 55 | + 'zip': 'application/zip', |
| 56 | + 'mp4': 'video/mp4', |
| 57 | + 'webm': 'video/webm', |
| 58 | + 'mp3': 'audio/mpeg', |
| 59 | + 'wav': 'audio/wav', |
| 60 | + 'woff': 'font/woff', |
| 61 | + 'woff2': 'font/woff2', |
| 62 | + 'ttf': 'font/ttf', |
| 63 | + 'otf': 'font/otf', |
| 64 | + 'wasm': 'application/wasm', |
| 65 | +}; |
| 66 | + |
40 | 67 | interface RenderHtmlOptions {
|
41 | 68 | /** Attrs passed to the <html> tag, e.g. `{lang: 'en'}`. */
|
42 | 69 | htmlAttrs?: preact.JSX.HTMLAttributes<HTMLHtmlElement>;
|
@@ -172,6 +199,35 @@ export class Renderer {
|
172 | 199 | res.end(html);
|
173 | 200 | };
|
174 | 201 |
|
| 202 | + if (route.module.getStaticContent) { |
| 203 | + let props: any; |
| 204 | + if (route.module.getStaticProps) { |
| 205 | + props = await route.module.getStaticProps({ |
| 206 | + rootConfig: this.rootConfig, |
| 207 | + params: routeParams, |
| 208 | + }); |
| 209 | + } else { |
| 210 | + props = {rootConfig: this.rootConfig, params: routeParams}; |
| 211 | + } |
| 212 | + const result = await route.module.getStaticContent(props); |
| 213 | + let body: string | Buffer; |
| 214 | + let contentType: string | undefined; |
| 215 | + if (typeof result === 'string' || Buffer.isBuffer(result)) { |
| 216 | + body = result; |
| 217 | + } else if (result && typeof result === 'object') { |
| 218 | + body = result.body; |
| 219 | + contentType = result.contentType; |
| 220 | + } else { |
| 221 | + body = ''; |
| 222 | + } |
| 223 | + res.status(200); |
| 224 | + const ext = path.extname(route.routePath); |
| 225 | + res.set({ |
| 226 | + 'Content-Type': contentType || guessContentType(ext), |
| 227 | + }); |
| 228 | + return res.end(body); |
| 229 | + } |
| 230 | + |
175 | 231 | if (route.module.handle) {
|
176 | 232 | const handlerContext: HandlerContext = {
|
177 | 233 | route: route,
|
@@ -452,7 +508,6 @@ export class Renderer {
|
452 | 508 | Object.keys(sitemap)
|
453 | 509 | .sort()
|
454 | 510 | .forEach((urlPath: string) => {
|
455 |
| - // console.log(urlPath); |
456 | 511 | const sitemapItem = sitemap[urlPath];
|
457 | 512 | const orderedAlts: Record<string, {hrefLang: string; urlPath: string}> =
|
458 | 513 | {};
|
@@ -746,3 +801,8 @@ function sortLocales(a: string, b: string) {
|
746 | 801 | }
|
747 | 802 | return a.localeCompare(b);
|
748 | 803 | }
|
| 804 | + |
| 805 | +function guessContentType(ext: string): string { |
| 806 | + const normalized = ext.trim().toLowerCase().replace(/^\./, ''); |
| 807 | + return CONTENT_TYPES[normalized] || 'application/octet-stream'; |
| 808 | +} |
0 commit comments