Skip to content

Commit 63f6338

Browse files
committed
put client in folder
1 parent 492a696 commit 63f6338

21 files changed

+111
-88
lines changed

eslint.config.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ export default [
7676
{
7777
files: [
7878
'src/generators/legacy-html/assets/*.js',
79-
'src/generators/web/hooks/*',
80-
'src/generators/web/components/*',
79+
'src/generators/web/client/**/*',
8180
],
8281
languageOptions: { globals: { ...globals.browser } },
8382
},

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"remark-stringify": "^11.0.0",
8080
"semver": "^7.7.1",
8181
"shiki": "^3.4.2",
82+
"sval": "^0.6.7",
8283
"tailwindcss": "^4.1.8",
8384
"unified": "^11.0.5",
8485
"unist-builder": "^4.0.0",

src/generators/web/components/CodeBox.jsx renamed to src/generators/web/client/components/CodeBox.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
2-
import { getLanguageDisplayName } from '@node-core/rehype-shiki';
32
import BaseCodeBox from '@node-core/ui-components/Common/BaseCodeBox';
3+
import { getLanguageDisplayName } from '@node-core/rehype-shiki';
44

55
const MDXCodeBox = ({ className, ...props }) => {
66
const matches = className?.match(/language-(?<language>[a-zA-Z]+)/);

src/generators/web/constants.mjs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,42 @@
11
import { fileURLToPath } from 'node:url';
22

33
export const ESBUILD_RESOLVE_DIR = fileURLToPath(
4-
new URL('./components', import.meta.url)
4+
new URL('./client', import.meta.url)
55
);
66

77
// Imports are relative to ESBUILD_RESOLVE_DIR
88
export const JSX_IMPORTS = {
99
NavBar: {
1010
name: 'NavBar',
11-
source: './NavBar.jsx',
11+
source: './components/NavBar.jsx',
1212
},
1313
SideBar: {
1414
name: 'SideBar',
15-
source: './SideBar.jsx',
15+
source: './components/SideBar.jsx',
1616
},
1717
MetaBar: {
1818
name: 'MetaBar',
19-
source: './MetaBar.jsx',
19+
source: './components/MetaBar.jsx',
2020
},
2121
Footer: {
2222
name: 'Footer',
23-
source: './Footer.jsx',
23+
source: './components/Footer.jsx',
2424
},
2525
ChangeHistory: {
2626
name: 'ChangeHistory',
27-
source: './ChangeHistory.jsx',
27+
source: './components/ChangeHistory.jsx',
2828
},
2929
CircularIcon: {
3030
name: 'CircularIcon',
31-
source: './CircularIcon.jsx',
31+
source: './components/CircularIcon.jsx',
3232
},
3333
CodeBox: {
3434
name: 'CodeBox',
35-
source: './CodeBox.jsx',
35+
source: './components/CodeBox.jsx',
3636
},
3737
CodeTabs: {
3838
name: 'CodeTabs',
39-
source: './CodeTabs.jsx',
39+
source: './components/CodeTabs.jsx',
4040
},
4141
Article: {
4242
name: 'Article',

src/generators/web/index.mjs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { readFile, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
33

44
import { generate } from '@babel/generator';
5+
import { estreeToBabel } from 'estree-to-babel';
56
import Mustache from 'mustache';
67

7-
import buildProgram from './utils/buildProgram.mjs';
8-
import bundleCode from './utils/bundleCode.mjs';
8+
import createASTBuilder from './utils/build.mjs';
9+
import bundleCode from './utils/bundle.mjs';
910

1011
/**
1112
* This generator generates a JavaScript / HTML / CSS bundle from the input JSX AST
@@ -33,19 +34,21 @@ export default {
3334
'utf-8'
3435
);
3536

37+
const { buildClientProgram } = createASTBuilder();
38+
3639
let css;
3740

3841
const bundles = await Promise.all(
3942
entries.map(async entry => {
40-
const program = buildProgram(entry);
41-
const { code } = generate(program);
42-
const bundled = await bundleCode(code);
43+
const { program } = estreeToBabel(entry);
44+
const clientCode = generate(buildClientProgram(program)).code;
4345

44-
// All bundles share the same CSS
45-
if (!css) {
46-
css = bundled.css;
47-
}
46+
const bundled = await bundleCode(clientCode);
47+
48+
// Extract CSS from first bundle only
49+
css ??= bundled.css;
4850

51+
// TODO: Remove mustache
4952
const rendered = Mustache.render(template, {
5053
title: entry.data.heading.data.name,
5154
javascript: bundled.js,
@@ -59,7 +62,7 @@ export default {
5962
})
6063
);
6164

62-
if (output) {
65+
if (output && css) {
6366
await writeFile(join(output, 'styles.css'), css);
6467
}
6568

src/generators/web/template.html

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html>
33
<head>
4-
<meta charset="UTF-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link rel="stylesheet" href="./styles.css" />
74
<title>{{title}}</title>
5+
<link rel="stylesheet" href="styles.css" />
86
</head>
97
<body>
10-
<div id="root"></div>
8+
<div id="root">{{{htmlContent}}}</div>
119
<script>{{{javascript}}}</script>
1210
</body>
1311
</html>

src/generators/web/utils/build.mjs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as t from '@babel/types';
2+
3+
import { JSX_IMPORTS } from '../constants.mjs';
4+
5+
/**
6+
* Creates an import declaration AST node
7+
* @param {string|null} defaultImport - The default import identifier name, or null for side-effect imports
8+
* @param {string} source - The module path to import from
9+
*/
10+
const importDeclaration = (defaultImport, source) =>
11+
t.importDeclaration(
12+
defaultImport
13+
? [t.importDefaultSpecifier(t.identifier(defaultImport))]
14+
: [],
15+
t.stringLiteral(source)
16+
);
17+
18+
/**
19+
* Creates standard React import declarations including React core and JSX components
20+
*/
21+
const reactImports = () => [
22+
importDeclaration('React', 'react'),
23+
...Object.values(JSX_IMPORTS).map(({ name, source }) =>
24+
importDeclaration(name, source)
25+
),
26+
];
27+
28+
/**
29+
* Creates an AST builder with hardcoded configuration for React SSR/hydration
30+
*/
31+
export default () => {
32+
const imports = reactImports();
33+
/**
34+
* Generates code that hydrates a server-rendered React component on the client
35+
* @param {import('@babel/types').Expression} component - The React component AST node to hydrate
36+
*/
37+
const buildClientProgram = component =>
38+
t.program([
39+
...imports,
40+
importDeclaration(null, '@node-core/ui-components/styles/index.css'),
41+
importDeclaration('ReactDOM', 'react-dom/client'),
42+
t.expressionStatement(
43+
t.callExpression(
44+
t.memberExpression(
45+
t.callExpression(
46+
t.memberExpression(
47+
t.identifier('ReactDOM'),
48+
t.identifier('createRoot')
49+
),
50+
[
51+
t.callExpression(
52+
t.memberExpression(
53+
t.identifier('document'),
54+
t.identifier('getElementById')
55+
),
56+
[t.stringLiteral('root')]
57+
),
58+
]
59+
),
60+
t.identifier('render')
61+
),
62+
[component]
63+
)
64+
),
65+
]);
66+
67+
return {
68+
buildClientProgram,
69+
};
70+
};

src/generators/web/utils/buildProgram.mjs

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/generators/web/utils/bundleCode.mjs renamed to src/generators/web/utils/bundle.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ const uiComponentsResolver = {
5353
/**
5454
* Bundles JavaScript code and returns JS/CSS content
5555
* @param {string} code - Source code to bundle
56+
* @param {boolean} server
5657
* @returns {Promise<{js: string, css: string}>}
5758
*/
58-
export default async code => {
59+
export default async (code, server) => {
5960
const result = await esbuild.build({
6061
stdin: {
6162
contents: code,
@@ -66,7 +67,7 @@ export default async code => {
6667
sourcemap: 'inline',
6768
format: 'iife',
6869
target: 'es2020',
69-
platform: 'browser',
70+
platform: server ? 'node' : 'browser',
7071
jsx: 'automatic',
7172
write: false,
7273
// This output file is a pseudo-file. It's never written to (`write: false`),

0 commit comments

Comments
 (0)