-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updated SSR worfklow for better readability and implemented some addi…
…tional error handling when serving / streaming static assets (#5)
- Loading branch information
Showing
11 changed files
with
237 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ const generator = new Generator({ | |
'koa-bodyparser', | ||
'http-errors', | ||
'pg', | ||
'mime' | ||
], | ||
}) | ||
|
||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
declare module 'cookie' | ||
declare module 'make-fetch-happen' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { readdir } from 'fs/promises' | ||
import dirname from '../server/lib/dirname' | ||
import { join, normalize } from 'path' | ||
|
||
const __dirname = dirname(import.meta) | ||
|
||
export const assetsPath = normalize(join(__dirname, '../.client')) | ||
|
||
export const htmlFiles = await readdir(normalize(join(__dirname, '../client/html'))) | ||
.then((files: Array<string>) => files.filter((f: string) => f.includes('.html'))) | ||
.then((files: Array<string>) => files.map((f: string) => f.replace('.html', ''))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { createEmotionCache } from '../common/app' | ||
import createEmotionServer from '@emotion/server/create-instance' | ||
import Layout from './_layout' | ||
import { renderToString } from 'react-dom/server' | ||
import { join, normalize } from 'path' | ||
import fs from 'fs/promises' | ||
|
||
const INDEX_NAME = 'somisana' | ||
|
||
export default async (ctx, htmlFiles, assetsPath) => { | ||
ctx.set('Content-type', 'text/html') | ||
const entry = ctx.request.url.replace('.html', '').replace('/', '') | ||
const page = entry ? (htmlFiles.includes(entry) ? entry : INDEX_NAME) : INDEX_NAME | ||
|
||
const htmlUtf8 = await fs.readFile(normalize(join(assetsPath, `${page}.html`)), { | ||
encoding: 'utf-8', | ||
}) | ||
|
||
const SsrEntry = await import(normalize(join(assetsPath, `ssr.${page}.js`))).then( | ||
({ default: C }) => C | ||
) | ||
|
||
const emotionCache = createEmotionCache() | ||
const { extractCriticalToChunks, constructStyleTagsFromChunks } = | ||
createEmotionServer(emotionCache) | ||
|
||
const html = renderToString( | ||
<Layout ctx={ctx} emotionCache={emotionCache}> | ||
<SsrEntry /> | ||
</Layout> | ||
) | ||
|
||
const emotionChunks = extractCriticalToChunks(html) | ||
const emotionCss = constructStyleTagsFromChunks(emotionChunks) | ||
|
||
const result = htmlUtf8 | ||
.replace('</title>', `</title>${emotionCss}`) | ||
.replace('<div id="root"></div>', `<div id="root">${html}</div>`) | ||
|
||
ctx.body = result | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { open } from 'fs/promises' | ||
|
||
export default async (ctx: any, contentType: string, filePath: string) => { | ||
let fd: any | ||
try { | ||
fd = await open(filePath) | ||
} catch (error) { | ||
ctx.status = 404 | ||
return | ||
} | ||
|
||
ctx.set('Content-type', contentType) | ||
ctx.body = fd.createReadStream() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default (path: string) => | ||
path.replace(/(explore)\/(.*)/, (match, g1, g2) => { | ||
if (path.endsWith('.js')) { | ||
return path.replace(`${g1}/`, '') | ||
} | ||
return match.replace(`${g1}/`, 'esri-atlas').replace(g2, '') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,23 @@ | ||
import { join, normalize } from 'path' | ||
import fs from 'fs/promises' | ||
import dirname from '../server/lib/dirname' | ||
import { createEmotionCache } from '../common/app' | ||
import createEmotionServer from '@emotion/server/create-instance' | ||
import Layout from './_layout' | ||
import serve from './_serve' | ||
import streamFile from './_stream-file' | ||
import renderHTML from './_render-html' | ||
import mime from 'mime' | ||
import rewrite from './_url-rewrite' | ||
import { assetsPath, htmlFiles } from './_register-assets' | ||
|
||
/** | ||
* Emotion doesn't support stream-rendering yet | ||
* Once it does, please update | ||
*/ | ||
import { renderToString } from 'react-dom/server' | ||
|
||
const INDEX_NAME = 'somisana' | ||
const __dirname = dirname(import.meta) | ||
const files = normalize(join(__dirname, '../.client')) | ||
const APP_ENTRIES = await fs | ||
.readdir(normalize(join(__dirname, '../client/html'))) | ||
.then(files => files.filter(f => f.includes('.html'))) | ||
.then(files => files.map(f => f.replace('.html', ''))) | ||
|
||
const rewrite = str => | ||
str.replace(/(explore)\/(.*)/, (match, g1, g2) => { | ||
if (str.endsWith('.js')) { | ||
return str.replace(`${g1}/`, '') | ||
} | ||
return match.replace(`${g1}/`, 'esri-atlas').replace(g2, '') | ||
}) | ||
|
||
export default async ctx => { | ||
export default async (ctx: any) => { | ||
const url = rewrite(ctx.request.url) | ||
|
||
if (url.endsWith('.txt')) { | ||
ctx.set('Content-type', 'text/plain') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else if (url.endsWith('.ico')) { | ||
ctx.set('Content-type', 'image/x-icon') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else if (url.endsWith('.js')) { | ||
ctx.set('Content-type', 'application/javascript; charset=utf-8') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else if (url.endsWith('.png')) { | ||
ctx.set('Content-type', 'image/png') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else if (url.endsWith('.css')) { | ||
ctx.set('Content-type', 'text/css') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else if (url.endsWith('site.webmanifest')) { | ||
ctx.set('Content-type', 'application/json; charset=utf-8') | ||
ctx.body = serve(ctx, { files, url }) | ||
} else { | ||
ctx.set('Content-type', 'text/html') | ||
const entry = ctx.request.url.replace('.html', '').replace('/', '') | ||
const page = entry ? (APP_ENTRIES.includes(entry) ? entry : INDEX_NAME) : INDEX_NAME | ||
|
||
const htmlUtf8 = await fs.readFile(normalize(join(files, `${page}.html`)), { | ||
encoding: 'utf-8', | ||
}) | ||
|
||
const SsrEntry = await import(normalize(join(files, `ssr.${page}.js`))).then( | ||
({ default: C }) => C | ||
) | ||
|
||
const emotionCache = createEmotionCache() | ||
const { extractCriticalToChunks, constructStyleTagsFromChunks } = | ||
createEmotionServer(emotionCache) | ||
|
||
const html = renderToString( | ||
<Layout ctx={ctx} emotionCache={emotionCache}> | ||
<SsrEntry /> | ||
</Layout> | ||
) | ||
|
||
const emotionChunks = extractCriticalToChunks(html) | ||
const emotionCss = constructStyleTagsFromChunks(emotionChunks) | ||
|
||
const result = htmlUtf8 | ||
.replace('</title>', `</title>${emotionCss}`) | ||
.replace('<div id="root"></div>', `<div id="root">${html}</div>`) | ||
|
||
ctx.body = result | ||
const contentType = mime.getType(url) | ||
const assetPath = normalize(join(assetsPath, url)) | ||
|
||
switch (contentType) { | ||
case 'text/html': | ||
case null: | ||
await renderHTML(ctx, htmlFiles, assetsPath) | ||
break | ||
|
||
default: | ||
await streamFile(ctx, contentType, assetPath) | ||
break | ||
} | ||
} |