Last active
July 11, 2023 16:32
-
-
Save MrFoxPro/50ef956ca8a95e55deff6b2fd0e1f459 to your computer and use it in GitHub Desktop.
Prerendering
This file contains hidden or 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
| import { renderToStringAsync } from 'solid-js/web' | |
| export function IndexPage() { | |
| return <div>Here will be a landing. But now loading App.</div> | |
| } | |
| export async function render() { | |
| const result = await renderToStringAsync(IndexPage) | |
| return { | |
| url: '/', | |
| template: './index.html', | |
| head: [], | |
| body: [result], | |
| include: [() => import('./app-loader')], | |
| } | |
| } |
This file contains hidden or 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
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" | |
| content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover" /> | |
| <!--head--> | |
| </head> | |
| <body class="@dark:(bg-#17181A color-white)"> | |
| <script type="module" src="./app.tsx"></script> | |
| <!--body--> | |
| </body> | |
| </html> |
This file contains hidden or 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
| import { resolve } from 'std/path/mod.ts' | |
| import { walkSync } from 'std/fs/mod.ts' | |
| import { build, createServer, type Rollup } from 'npm:vite' | |
| import express from 'https://esm.sh/[email protected]' | |
| export interface IRenderOutput { | |
| url: string | |
| template: string | |
| head: string[] | |
| body: string[] | |
| include: (() => Promise<unknown>)[] | |
| } | |
| const command = Deno.args[0] | |
| if (command && !['build', 'dev', ''].includes(command)) { | |
| console.error('Wrong command') | |
| Deno.exit(1) | |
| } | |
| const root = new URL('.', import.meta.url).pathname | |
| const walkResult = walkSync(root, { | |
| match: [/.+entry\.(tsx?|mts)/], | |
| skip: [/node_modules/, /\.git/, /dist/], | |
| }) | |
| const entryFiles = Array.from(walkResult) | |
| if (!command || command == 'dev') { | |
| const app = express() | |
| const vite = await createServer({ | |
| root, | |
| appType: 'custom', | |
| logLevel: 'info', | |
| server: { | |
| middlewareMode: true, | |
| watch: { | |
| usePolling: true, | |
| interval: 100, | |
| }, | |
| }, | |
| }) | |
| const tasks = entryFiles.map(e => vite.ssrLoadModule(e.path).then(mod => mod.render() as IRenderOutput)) | |
| const modules = await Promise.all(tasks) | |
| // @ts-ignore | |
| app.use(vite.middlewares) | |
| app.use('*', async (req, res, next) => { | |
| const url = req.path | |
| const out = modules.find(x => x.url == url) | |
| if (!out) return next() | |
| try { | |
| const template = await renderTemplate(out) | |
| const html = await vite.transformIndexHtml(url, template) | |
| res.status(200).end(html) | |
| } catch (e) { | |
| vite.ssrFixStacktrace(e) | |
| console.log(e.stack) | |
| res.status(500).end(e.stack) | |
| } | |
| }) | |
| app.listen(3000, () => { | |
| console.log(`Dev server is running on https://localhost:3000`) | |
| }) | |
| } else if (command == 'build') { | |
| const clientOut = resolve(root, 'dist') | |
| const ssrOut = resolve(root, 'dist/_ssr') | |
| const entries = entryFiles.map(e => resolve(root, e.path)) | |
| await build({ | |
| root, | |
| build: { | |
| rollupOptions: { | |
| input: ['index.html'].concat(entries), | |
| }, | |
| outDir: clientOut, | |
| }, | |
| }) | |
| const ssr = (await build({ | |
| build: { | |
| ssr: true, | |
| outDir: ssrOut, | |
| rollupOptions: { input: entries }, | |
| }, | |
| })) as Rollup.RollupOutput | |
| const builtEntries = ssr.output.filter(chunk => | |
| // @ts-ignore | |
| entryFiles.some(file => file.path == (chunk.facadeModuleId as string)) | |
| ) | |
| for (const entry of builtEntries) { | |
| const mod = await import(resolve(ssrOut, entry.fileName)) | |
| const out = (await mod.render()) as IRenderOutput | |
| const html = await renderTemplate(out, resolve(clientOut, out.template)) | |
| const entryHTMLPathname = out.url == '/' ? 'index.html' : out.url + '.html' | |
| await Deno.writeTextFile(resolve(clientOut, entryHTMLPathname), html) | |
| console.log(`🎉 Page ${entryHTMLPathname} generated!`) | |
| } | |
| await Deno.remove(ssrOut, { recursive: true }) | |
| } | |
| function getUrlFromImport<T>(modFunc: () => Promise<T>) { | |
| const regex = /import(__)?\(['|"](?<path>.+)['|"]\)/ | |
| const result = modFunc.toString().match(regex).groups.path | |
| if (!result) throw new Error(`Can\'t parse path from ${modFunc.toString()}`) | |
| return result | |
| } | |
| /* | |
| We can't drop reading HTML file, because Vite can handle injection of CSS and other critical things only by providing file | |
| Do it otherwise, will be too complicated | |
| */ | |
| export async function renderTemplate( | |
| { template, body, head, include }: IRenderOutput, | |
| overridePath?: string | |
| ) { | |
| const html = await Deno.readFile(overridePath ?? template).then(r => new TextDecoder('utf-8').decode(r)) | |
| const headString = head.join('\n') | |
| const bodyString = | |
| body.join('\n') + | |
| include | |
| .map(i => getUrlFromImport(i)) | |
| .map(e => `<script type="module" src="${e}"></script>`) | |
| .join('\n') | |
| return html.replace('<!--head-->', headString).replace('<!--body-->', bodyString) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment