-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathprocessing.mjs
More file actions
90 lines (79 loc) · 4.1 KB
/
processing.mjs
File metadata and controls
90 lines (79 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import HTMLMinifier from '@minify-html/node';
import { toJs, jsx } from 'estree-util-to-js';
import Mustache from 'mustache';
import bundleCode from './bundle.mjs';
// Generate a unique variable name to capture the result of the server-side code.
// This prevents naming conflicts.
const SSRvariable = `_${Math.random().toString(36).slice(2)}`;
/**
* Executes server-side JavaScript code in a safe, isolated context.
* This function takes a string of JavaScript code, bundles it, and then runs it
* within a new Function constructor to prevent scope pollution and allow for
* dynamic module loading via a provided `require` function.
* The result of the server-side execution is expected to be assigned to a
* dynamically generated variable name, which is then returned.
*
* @param {string} serverCode - The server-side JavaScript code to execute as a string.
* @param {ReturnType<import('node:module').createRequire>} requireFn - A Node.js `require` function
*/
export async function executeServerCode(serverCode, requireFn) {
// Bundle the server-side code. This step resolves imports and prepares the code
// for execution, ensuring all necessary dependencies are self-contained.
const { js: bundledServer } = await bundleCode(serverCode, { server: true });
// Create a new Function from the bundled server code.
// The `require` argument is passed into the function's scope, allowing the
// `bundledServer` code to use it for dynamic imports.
// The `return ${variable};` statement ensures that the value assigned to
// the dynamic variable within the `bundledServer` code is returned by this function.
const executedFunction = new Function(
'require',
`${bundledServer}\nreturn ${SSRvariable};`
);
// Execute the dynamically created function with the provided `requireFn`.
// The result of this execution is the dehydrated content from the server-side rendering.
return executedFunction(requireFn);
}
/**
* Processes a single JSX AST (Abstract Syntax Tree) entry to generate a complete
* HTML page, including server-side rendered content, client-side JavaScript, and CSS.
*
* @param {import('../jsx-ast/utils/buildContent.mjs').JSXContent} entry - The JSX AST entry to process.
* @param {string} template - The HTML template string that serves as the base for the output page.
* @param {ReturnType<import('./generate.mjs')>} astBuilders - The AST generators
* @param {version} version - The version to generator the documentation for
* @param {ReturnType<import('node:module').createRequire>} requireFn - A Node.js `require` function.
*/
export async function processJSXEntry(
entry,
template,
{ buildServerProgram, buildClientProgram },
requireFn,
{ version }
) {
// `estree-util-to-js` with the `jsx` handler converts the AST nodes into a string
// that represents the equivalent JavaScript code, including JSX syntax.
const { value: code } = toJs(entry, { handlers: jsx });
// `buildServerProgram` takes the JSX-derived code and prepares it for server execution.
// `executeServerCode` then runs this code in a Node.js environment to produce
// the initial HTML content (dehydrated state) that will be sent to the client.
const serverCode = buildServerProgram(code, SSRvariable);
const dehydrated = await executeServerCode(serverCode, requireFn);
// `buildClientProgram` prepares the JSX-derived code for client-side execution.
// `bundleCode` then bundles this client-side code, resolving imports and
// potentially generating associated CSS. This bundle will hydrate the SSR content.
const clientCode = buildClientProgram(code);
const clientBundle = await bundleCode(clientCode);
// TODO(@avivkeller): Don't depend on Mustache
const renderedHtml = Mustache.render(template, {
title: `${entry.data.heading.data.name} | Node.js v${version} Documentation`,
dehydrated,
javascript: clientBundle.js,
});
// The input to `minify` must be a Buffer.
const finalHTMLBuffer = HTMLMinifier.minify(Buffer.from(renderedHtml), {});
// Return the generated HTML and any CSS produced by the client bundle.
return {
html: finalHTMLBuffer,
css: clientBundle.css,
};
}