@@ -3,25 +3,24 @@ import { createRequire } from 'node:module';
33import { join } from 'node:path' ;
44
55import { estreeToBabel } from 'estree-to-babel' ;
6- import Mustache from 'mustache ' ;
6+ import { minify } from 'html-minifier-terser ' ;
77
8- import { ESBUILD_RESOLVE_DIR } from './constants.mjs' ;
8+ import { ESBUILD_RESOLVE_DIR , TEMPLATE_PLACEHOLDERS } from './constants.mjs' ;
9+ import { TERSER_MINIFY_OPTIONS } from '../../constants.mjs' ;
910import createASTBuilder from './utils/astBuilder.mjs' ;
1011import bundleCode from './utils/bundle.mjs' ;
1112
1213/**
1314 * Executes server-side code in a safe, isolated context
1415 * @param {string } serverCode - The server code to execute
15- * @param {Function } require - Node.js require function for dependencies
16+ * @param {ReturnType<createRequire> } require - Node.js require function for dependencies
1617 * @returns {Promise<string> } The rendered HTML output
1718 */
1819async function executeServerCode ( serverCode , require ) {
19- // Bundle the server code for execution
2020 const { js : bundledServer } = await bundleCode ( serverCode , {
2121 platform : 'node' ,
2222 } ) ;
2323
24- // Create a safe execution context that returns the rendered content
2524 const executedFunction = new Function (
2625 'require' ,
2726 `
@@ -34,6 +33,53 @@ async function executeServerCode(serverCode, require) {
3433 return executedFunction ( require ) ;
3534}
3635
36+ /**
37+ * Processes a single entry and writes the HTML file immediately
38+ * @param {import('../jsx-ast/utils/buildContent.mjs').JSXContent } entry - JSX AST entry
39+ * @param {string } template - HTML template
40+ * @param {ReturnType<createASTBuilder> } astBuilders - AST builder functions
41+ * @param {ReturnType<createRequire> } require - Node.js require function
42+ * @param {string } output - Output directory path
43+ * @returns {Promise<{html: string, css?: string}> }
44+ */
45+ async function processEntry (
46+ entry ,
47+ template ,
48+ { buildServerProgram, buildClientProgram } ,
49+ require ,
50+ output
51+ ) {
52+ // Convert JSX AST to Babel AST
53+ const { program } = estreeToBabel ( entry ) ;
54+
55+ // Generate and execute server-side code for SSR
56+ const serverCode = buildServerProgram ( program ) ;
57+ const serverRenderedHTML = await executeServerCode ( serverCode , require ) ;
58+
59+ // Generate and bundle client-side code
60+ const clientCode = buildClientProgram ( program ) ;
61+ const clientBundle = await bundleCode ( clientCode ) ;
62+
63+ // Render the final HTML using the template
64+ const finalHTML = await minify (
65+ template
66+ . replace ( TEMPLATE_PLACEHOLDERS . TITLE , entry . data . heading . data . name )
67+ . replace ( TEMPLATE_PLACEHOLDERS . DEHYDRATED , serverRenderedHTML )
68+ . replace ( TEMPLATE_PLACEHOLDERS . JAVASCRIPT , clientBundle . js ) ,
69+ TERSER_MINIFY_OPTIONS
70+ ) ;
71+
72+ // Write HTML file immediately if output directory is specified
73+ if ( output ) {
74+ await writeFile ( join ( output , `${ entry . data . api } .html` ) , finalHTML , 'utf-8' ) ;
75+ }
76+
77+ return {
78+ html : finalHTML ,
79+ css : clientBundle . css ,
80+ } ;
81+ }
82+
3783/**
3884 * This generator generates a JavaScript / HTML / CSS bundle from the input JSX AST
3985 *
@@ -54,57 +100,26 @@ export default {
54100 * @param {Partial<GeneratorOptions> } options
55101 */
56102 async generate ( entries , { output } ) {
57- // Load the HTML template
103+ // Load template and set up dependencies
58104 const template = await readFile (
59105 new URL ( 'template.html' , import . meta. url ) ,
60106 'utf-8'
61107 ) ;
62-
63- // Set up AST builders for server and client code
64- const { buildServerProgram, buildClientProgram } = createASTBuilder ( ) ;
108+ const astBuilders = createASTBuilder ( ) ;
65109 const require = createRequire ( ESBUILD_RESOLVE_DIR ) ;
66110
67- let css ; // Will store CSS from the first bundle
68-
69- // Process each entry in parallel
70- const bundles = await Promise . all (
71- entries . map ( async entry => {
72- // Convert JSX AST to Babel AST
73- const { program } = estreeToBabel ( entry ) ;
74-
75- // Generate and execute server-side code for SSR
76- const serverCode = buildServerProgram ( program ) ;
77- const serverRenderedHTML = await executeServerCode ( serverCode , require ) ;
78-
79- // Generate and bundle client-side code
80- const clientCode = buildClientProgram ( program ) ;
81- const clientBundle = await bundleCode ( clientCode ) ;
82-
83- // Extract CSS only from the first bundle to avoid duplicates
84- css ??= clientBundle . css ;
85-
86- // Render the final HTML using the template
87- const finalHTML = Mustache . render ( template , {
88- title : entry . data . heading . data . name ,
89- javascript : clientBundle . js ,
90- dehydrated : serverRenderedHTML ,
91- } ) ;
92-
93- // Write individual HTML file if output directory is specified
94- if ( output ) {
95- const filename = `${ entry . data . api } .html` ;
96- await writeFile ( join ( output , filename ) , finalHTML ) ;
97- }
98-
99- return finalHTML ;
100- } )
111+ // Process all entries in parallel
112+ const results = await Promise . all (
113+ entries . map ( entry => processEntry ( entry , template , astBuilders , require ) )
101114 ) ;
102115
103- // Write shared CSS file if we have CSS and an output directory
104- if ( output && css ) {
105- await writeFile ( join ( output , 'styles.css' ) , css ) ;
116+ if ( output ) {
117+ await writeFile (
118+ join ( output , 'styles.css' ) ,
119+ results . find ( result => result . css ) . css
120+ ) ;
106121 }
107122
108- return bundles ;
123+ return results . map ( result => result . html ) ;
109124 } ,
110125} ;
0 commit comments