@punkestu/react-html 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +215 -2
  2. package/bin/react-html.js +8 -0
  3. package/dist/cli/docs-content.d.ts +8 -0
  4. package/dist/cli/docs-content.d.ts.map +1 -0
  5. package/dist/cli/docs-content.js +261 -0
  6. package/dist/cli/docs-content.js.map +1 -0
  7. package/dist/cli/index.d.ts +2 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +113 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/cli/tui-docs.d.ts +5 -0
  12. package/dist/cli/tui-docs.d.ts.map +1 -0
  13. package/dist/cli/tui-docs.js +191 -0
  14. package/dist/cli/tui-docs.js.map +1 -0
  15. package/dist/client/hydrate.d.ts +4 -0
  16. package/dist/client/hydrate.d.ts.map +1 -0
  17. package/dist/client/hydrate.js +80 -0
  18. package/dist/client/hydrate.js.map +1 -0
  19. package/dist/core/bundler.d.ts +7 -0
  20. package/dist/core/bundler.d.ts.map +1 -0
  21. package/dist/core/bundler.js +103 -0
  22. package/dist/core/bundler.js.map +1 -0
  23. package/dist/core/formatter.d.ts +6 -0
  24. package/dist/core/formatter.d.ts.map +1 -0
  25. package/dist/core/formatter.js +74 -0
  26. package/dist/core/formatter.js.map +1 -0
  27. package/dist/core/island-client.d.ts +9 -0
  28. package/dist/core/island-client.d.ts.map +1 -0
  29. package/dist/core/island-client.js +8 -0
  30. package/dist/core/island-client.js.map +1 -0
  31. package/dist/core/island-registry.d.ts +15 -0
  32. package/dist/core/island-registry.d.ts.map +1 -0
  33. package/dist/core/island-registry.js +17 -0
  34. package/dist/core/island-registry.js.map +1 -0
  35. package/dist/core/island.d.ts +32 -0
  36. package/dist/core/island.d.ts.map +1 -0
  37. package/dist/core/island.js +47 -0
  38. package/dist/core/island.js.map +1 -0
  39. package/dist/core/loader.d.ts +11 -0
  40. package/dist/core/loader.d.ts.map +1 -0
  41. package/dist/core/loader.js +73 -0
  42. package/dist/core/loader.js.map +1 -0
  43. package/dist/core/renderer.d.ts +20 -0
  44. package/dist/core/renderer.d.ts.map +1 -0
  45. package/dist/core/renderer.js +53 -0
  46. package/dist/core/renderer.js.map +1 -0
  47. package/dist/index.d.ts +23 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +104 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/server/dev-server.d.ts +8 -0
  52. package/dist/server/dev-server.d.ts.map +1 -0
  53. package/dist/server/dev-server.js +112 -0
  54. package/dist/server/dev-server.js.map +1 -0
  55. package/dist/server/engine.d.ts +46 -0
  56. package/dist/server/engine.d.ts.map +1 -0
  57. package/dist/server/engine.js +222 -0
  58. package/dist/server/engine.js.map +1 -0
  59. package/dist/server/index.d.ts +5 -0
  60. package/dist/server/index.d.ts.map +1 -0
  61. package/dist/server/index.js +3 -0
  62. package/dist/server/index.js.map +1 -0
  63. package/package.json +47 -3
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { renderToString } from 'react-dom/server';
4
+ import { registerIslandInContext } from './island-registry.js';
5
+ let islandCounter = 0;
6
+ export function Island({ name, src, component: Component, props = {}, client = 'load', as: Tag = 'div', className, style, children }) {
7
+ const componentName = name ||
8
+ Component.__islandName ||
9
+ Component.displayName ||
10
+ Component.name ||
11
+ 'AnonymousIsland';
12
+ const islandId = `island-${++islandCounter}`;
13
+ // If src is specified or attached to component
14
+ const entryPath = src || Component.__islandPath || '';
15
+ registerIslandInContext({
16
+ id: islandId,
17
+ name: componentName,
18
+ entryPath,
19
+ props,
20
+ clientDirective: client
21
+ });
22
+ const Wrapper = Tag;
23
+ // Use renderToString for the island's inner tree to produce hydration-compatible SSR markup
24
+ const innerHtml = renderToString(React.createElement(Component, props, children));
25
+ return (_jsx(Wrapper, { "data-island": componentName, "data-island-id": islandId, "data-props": JSON.stringify(props), "data-client": client, className: className, style: style, dangerouslySetInnerHTML: { __html: `<!--__ISLAND_START__-->${innerHtml}<!--__ISLAND_END__-->` } }));
26
+ }
27
+ /**
28
+ * Helper to wrap any React component into a ready-to-use Island component
29
+ * Example:
30
+ * export const Counter = island(CounterComponent, import.meta.url, 'Counter');
31
+ * // In page: <Counter initialCount={5} />
32
+ */
33
+ export function island(Component, sourcePath, customName) {
34
+ const resolvedName = customName || Component.displayName || Component.name || 'AnonymousIsland';
35
+ Component.__islandPath = sourcePath;
36
+ Component.__islandName = resolvedName;
37
+ const IslandWrapper = (props) => {
38
+ const { client, as, className, style, ...componentProps } = props;
39
+ return (_jsx(Island, { name: resolvedName, src: sourcePath, component: Component, props: componentProps, client: client, as: as, className: className, style: style }));
40
+ };
41
+ IslandWrapper.displayName = `Island(${resolvedName})`;
42
+ IslandWrapper.__islandPath = sourcePath;
43
+ IslandWrapper.__islandName = resolvedName;
44
+ IslandWrapper.__rawComponent = Component;
45
+ return IslandWrapper;
46
+ }
47
+ //# sourceMappingURL=island.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island.js","sourceRoot":"","sources":["../../src/core/island.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAmC,MAAM,OAAO,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,IAAI,aAAa,GAAG,CAAC,CAAC;AAoBtB,MAAM,UAAU,MAAM,CAAoC,EACxD,IAAI,EACJ,GAAG,EACH,SAAS,EAAE,SAAS,EACpB,KAAK,GAAG,EAAO,EACf,MAAM,GAAG,MAAM,EACf,EAAE,EAAE,GAAG,GAAG,KAAK,EACf,SAAS,EACT,KAAK,EACL,QAAQ,EACO;IACf,MAAM,aAAa,GACjB,IAAI;QACH,SAAiB,CAAC,YAAY;QAC/B,SAAS,CAAC,WAAW;QACrB,SAAS,CAAC,IAAI;QACd,iBAAiB,CAAC;IAEpB,MAAM,QAAQ,GAAG,UAAU,EAAE,aAAa,EAAE,CAAC;IAE7C,+CAA+C;IAC/C,MAAM,SAAS,GAAG,GAAG,IAAK,SAAiB,CAAC,YAAY,IAAI,EAAE,CAAC;IAE/D,uBAAuB,CAAC;QACtB,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,aAAa;QACnB,SAAS;QACT,KAAK;QACL,eAAe,EAAE,MAAM;KACxB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,GAAU,CAAC;IAE3B,4FAA4F;IAC5F,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElF,OAAO,CACL,KAAC,OAAO,mBACO,aAAa,oBACV,QAAQ,gBACZ,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBACpB,MAAM,EACnB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,EACZ,uBAAuB,EAAE,EAAE,MAAM,EAAE,0BAA0B,SAAS,uBAAuB,EAAE,GAC/F,CACH,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CACpB,SAA2B,EAC3B,UAAkB,EAClB,UAAmB;IAEnB,MAAM,YAAY,GAAG,UAAU,IAAI,SAAS,CAAC,WAAW,IAAI,SAAS,CAAC,IAAI,IAAI,iBAAiB,CAAC;IAC/F,SAAiB,CAAC,YAAY,GAAG,UAAU,CAAC;IAC5C,SAAiB,CAAC,YAAY,GAAG,YAAY,CAAC;IAE/C,MAAM,aAAa,GAAG,CAAC,KAAiH,EAAE,EAAE;QAC1I,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,cAAc,EAAE,GAAG,KAAK,CAAC;QAClE,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,cAAmB,EAC1B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,GACZ,CACH,CAAC;IACJ,CAAC,CAAC;IAEF,aAAa,CAAC,WAAW,GAAG,UAAU,YAAY,GAAG,CAAC;IACrD,aAAqB,CAAC,YAAY,GAAG,UAAU,CAAC;IAChD,aAAqB,CAAC,YAAY,GAAG,YAAY,CAAC;IAClD,aAAqB,CAAC,cAAc,GAAG,SAAS,CAAC;IAClD,OAAO,aAAa,CAAC;AACvB,CAAC"}
@@ -0,0 +1,11 @@
1
+ export interface LoadedPage {
2
+ Component: React.ComponentType<any>;
3
+ metadata?: {
4
+ title?: string;
5
+ description?: string;
6
+ [key: string]: unknown;
7
+ };
8
+ }
9
+ export declare function loadPageModule(pageFilePath: string): Promise<LoadedPage>;
10
+ export declare function clearCache(): Promise<void>;
11
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/core/loader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAyBD,wBAAsB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA0C9E;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhD"}
@@ -0,0 +1,73 @@
1
+ import * as esbuild from 'esbuild';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { pathToFileURL } from 'node:url';
6
+ const CACHE_DIR = path.resolve(process.cwd(), '.react-html/cache');
7
+ // Plugin to replace import.meta.url with the real source file URL
8
+ const importMetaUrlPlugin = {
9
+ name: 'preserve-import-meta-url',
10
+ setup(build) {
11
+ build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
12
+ if (args.path.includes('node_modules'))
13
+ return null;
14
+ let text = await fs.readFile(args.path, 'utf8');
15
+ if (text.includes('import.meta.url')) {
16
+ const fileUrl = pathToFileURL(args.path).href;
17
+ text = text.replaceAll('import.meta.url', JSON.stringify(fileUrl));
18
+ const ext = path.extname(args.path).slice(1);
19
+ return {
20
+ contents: text,
21
+ loader: (ext === 'js' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') ? ext : 'tsx'
22
+ };
23
+ }
24
+ return null;
25
+ });
26
+ }
27
+ };
28
+ export async function loadPageModule(pageFilePath) {
29
+ await fs.mkdir(CACHE_DIR, { recursive: true });
30
+ const resolvedPath = path.resolve(pageFilePath);
31
+ const hash = crypto.createHash('sha256').update(resolvedPath).digest('hex').slice(0, 12);
32
+ const cacheFilePath = path.join(CACHE_DIR, `page-${hash}.mjs`);
33
+ // Build the page bundle for Node.js execution
34
+ await esbuild.build({
35
+ entryPoints: [resolvedPath],
36
+ outfile: cacheFilePath,
37
+ bundle: true,
38
+ format: 'esm',
39
+ platform: 'node',
40
+ target: 'node22',
41
+ packages: 'external',
42
+ sourcemap: 'inline',
43
+ plugins: [importMetaUrlPlugin],
44
+ loader: {
45
+ '.tsx': 'tsx',
46
+ '.ts': 'ts',
47
+ '.jsx': 'jsx',
48
+ '.js': 'js',
49
+ '.css': 'empty'
50
+ }
51
+ });
52
+ // Dynamic import with timestamp cache buster
53
+ const fileUrl = `${pathToFileURL(cacheFilePath).href}?t=${Date.now()}`;
54
+ const importedModule = await import(fileUrl);
55
+ const Component = importedModule.default;
56
+ if (!Component) {
57
+ throw new Error(`Page "${pageFilePath}" does not have a default export. Please provide "export default function Page()".`);
58
+ }
59
+ const { default: _, ...metadata } = importedModule;
60
+ return {
61
+ Component,
62
+ metadata
63
+ };
64
+ }
65
+ export async function clearCache() {
66
+ try {
67
+ await fs.rm(CACHE_DIR, { recursive: true, force: true });
68
+ }
69
+ catch {
70
+ // Ignore cache cleanup errors
71
+ }
72
+ }
73
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/core/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { IslandInstance } from './island-registry.js';
3
+ export interface RenderResult {
4
+ html: string;
5
+ islands: IslandInstance[];
6
+ }
7
+ export interface RenderOptions {
8
+ pagePath: string;
9
+ Component: React.ComponentType<any>;
10
+ props?: Record<string, unknown>;
11
+ metadata?: {
12
+ title?: string;
13
+ description?: string;
14
+ lang?: string;
15
+ [key: string]: unknown;
16
+ };
17
+ basePath?: string;
18
+ }
19
+ export declare function renderPageToHtml(options: RenderOptions): RenderResult;
20
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/core/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAwB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG5E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAuDrE"}
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { runWithRenderContext } from './island-registry.js';
4
+ import { formatHtml } from './formatter.js';
5
+ export function renderPageToHtml(options) {
6
+ const { pagePath, Component, props = {}, metadata = {}, basePath = '' } = options;
7
+ const islands = [];
8
+ const rawMarkup = runWithRenderContext({ pagePath, islands }, () => renderToStaticMarkup(React.createElement(Component, props)));
9
+ let finalHtml = rawMarkup;
10
+ const hasHtmlTag = /<html[^>]*>/i.test(finalHtml);
11
+ const hasDoctype = /<!DOCTYPE html>/i.test(finalHtml);
12
+ // If component returned only body contents / fragments, wrap in standard HTML shell
13
+ if (!hasHtmlTag) {
14
+ const lang = metadata.lang || 'en';
15
+ const titleTag = metadata.title ? `<title>${metadata.title}</title>` : '';
16
+ const descTag = metadata.description ? `<meta name="description" content="${metadata.description}">` : '';
17
+ finalHtml = `<!DOCTYPE html>
18
+ <html lang="${lang}">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ ${titleTag}
23
+ ${descTag}
24
+ </head>
25
+ <body>
26
+ ${finalHtml}
27
+ </body>
28
+ </html>`;
29
+ }
30
+ else if (!hasDoctype) {
31
+ finalHtml = `<!DOCTYPE html>\n${finalHtml}`;
32
+ }
33
+ // If there are islands, inject script tags before </body> or at the end of the file
34
+ if (islands.length > 0) {
35
+ // Collect unique island names
36
+ const uniqueIslands = Array.from(new Set(islands.map(i => i.name)));
37
+ const assetPrefix = basePath ? (basePath.endsWith('/') ? basePath : `${basePath}/`) : '/';
38
+ const scriptTags = uniqueIslands
39
+ .map(name => `<script type="module" src="${assetPrefix}assets/islands/${name}.js"></script>`)
40
+ .join('\n');
41
+ if (/<\/body>/i.test(finalHtml)) {
42
+ finalHtml = finalHtml.replace(/<\/body>/i, `${scriptTags}\n</body>`);
43
+ }
44
+ else {
45
+ finalHtml = `${finalHtml}\n${scriptTags}`;
46
+ }
47
+ }
48
+ return {
49
+ html: formatHtml(finalHtml),
50
+ islands
51
+ };
52
+ }
53
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/core/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAkB,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAoB5C,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAClF,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,MAAM,SAAS,GAAG,oBAAoB,CACpC,EAAE,QAAQ,EAAE,OAAO,EAAE,EACrB,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAClE,CAAC;IAEF,IAAI,SAAS,GAAG,SAAS,CAAC;IAC1B,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtD,oFAAoF;IACpF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC;QACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,QAAQ,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,qCAAqC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1G,SAAS,GAAG;cACF,IAAI;;;;IAId,QAAQ;IACR,OAAO;;;IAGP,SAAS;;QAEL,CAAC;IACP,CAAC;SAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACvB,SAAS,GAAG,oBAAoB,SAAS,EAAE,CAAC;IAC9C,CAAC;IAED,oFAAoF;IACpF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,8BAA8B;QAC9B,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1F,MAAM,UAAU,GAAG,aAAa;aAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,8BAA8B,WAAW,kBAAkB,IAAI,gBAAgB,CAAC;aAC5F,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,UAAU,WAAW,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,GAAG,SAAS,KAAK,UAAU,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;QAC3B,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ export { Island, island } from './core/island.js';
2
+ export type { IslandProps } from './core/island.js';
3
+ export { hydrateIsland } from './client/hydrate.js';
4
+ export { renderPageToHtml } from './core/renderer.js';
5
+ export { createEngine, ReactHtmlEngine } from './server/engine.js';
6
+ export type { EngineOptions } from './server/engine.js';
7
+ export interface BuildConfig {
8
+ root?: string;
9
+ pagesDir?: string;
10
+ outDir?: string;
11
+ publicDir?: string;
12
+ minify?: boolean;
13
+ }
14
+ export interface BuildResult {
15
+ pages: {
16
+ input: string;
17
+ output: string;
18
+ }[];
19
+ islands: string[];
20
+ outDir: string;
21
+ }
22
+ export declare function build(config?: BuildConfig): Promise<BuildResult>;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAqBD,wBAAsB,KAAK,CAAC,MAAM,GAAE,WAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAyF1E"}
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { loadPageModule, clearCache } from './core/loader.js';
4
+ import { renderPageToHtml } from './core/renderer.js';
5
+ import { bundleIslands } from './core/bundler.js';
6
+ export { Island, island } from './core/island.js';
7
+ export { hydrateIsland } from './client/hydrate.js';
8
+ export { renderPageToHtml } from './core/renderer.js';
9
+ export { createEngine, ReactHtmlEngine } from './server/engine.js';
10
+ async function findFilesRecursively(dir, extensions) {
11
+ const entries = await fs.readdir(dir, { withFileTypes: true });
12
+ const files = [];
13
+ for (const entry of entries) {
14
+ const fullPath = path.join(dir, entry.name);
15
+ if (entry.isDirectory()) {
16
+ files.push(...(await findFilesRecursively(fullPath, extensions)));
17
+ }
18
+ else if (entry.isFile()) {
19
+ const ext = path.extname(entry.name);
20
+ if (extensions.includes(ext) && !entry.name.startsWith('_') && !entry.name.includes('.test.')) {
21
+ files.push(fullPath);
22
+ }
23
+ }
24
+ }
25
+ return files;
26
+ }
27
+ export async function build(config = {}) {
28
+ const root = path.resolve(config.root || process.cwd());
29
+ const outDir = path.resolve(root, config.outDir || 'dist');
30
+ const minify = config.minify ?? true;
31
+ // Locate pages directory
32
+ let pagesDir = config.pagesDir ? path.resolve(root, config.pagesDir) : '';
33
+ if (!pagesDir) {
34
+ const candidateDirs = ['src/pages', 'pages'];
35
+ for (const cand of candidateDirs) {
36
+ const p = path.join(root, cand);
37
+ try {
38
+ const stat = await fs.stat(p);
39
+ if (stat.isDirectory()) {
40
+ pagesDir = p;
41
+ break;
42
+ }
43
+ }
44
+ catch {
45
+ // Continue checking
46
+ }
47
+ }
48
+ }
49
+ if (!pagesDir) {
50
+ throw new Error(`Pages directory not found. Please create 'src/pages' or specify 'pagesDir'.`);
51
+ }
52
+ // Ensure output directory exists and is clean
53
+ await fs.mkdir(outDir, { recursive: true });
54
+ // Copy public static directory if present
55
+ const publicDir = path.resolve(root, config.publicDir || 'public');
56
+ try {
57
+ const publicStat = await fs.stat(publicDir);
58
+ if (publicStat.isDirectory()) {
59
+ await fs.cp(publicDir, outDir, { recursive: true });
60
+ }
61
+ }
62
+ catch {
63
+ // Public directory optional
64
+ }
65
+ const pageFiles = await findFilesRecursively(pagesDir, ['.tsx', '.jsx', '.ts', '.js']);
66
+ if (pageFiles.length === 0) {
67
+ console.warn(`[react-html] No pages found in ${pagesDir}`);
68
+ }
69
+ const allDiscoveredIslands = [];
70
+ const processedPages = [];
71
+ for (const pageFile of pageFiles) {
72
+ const relativePage = path.relative(pagesDir, pageFile);
73
+ const parsed = path.parse(relativePage);
74
+ // Compute destination path: index.tsx -> index.html, about.tsx -> about.html
75
+ const outHtmlFile = path.join(outDir, parsed.dir, `${parsed.name}.html`);
76
+ await fs.mkdir(path.dirname(outHtmlFile), { recursive: true });
77
+ // Load page module
78
+ const { Component, metadata } = await loadPageModule(pageFile);
79
+ // Render to pure HTML and extract islands
80
+ const { html, islands } = renderPageToHtml({
81
+ pagePath: pageFile,
82
+ Component,
83
+ metadata
84
+ });
85
+ await fs.writeFile(outHtmlFile, html, 'utf8');
86
+ processedPages.push({ input: pageFile, output: outHtmlFile });
87
+ if (islands.length > 0) {
88
+ allDiscoveredIslands.push(...islands);
89
+ }
90
+ }
91
+ // Bundle reactive islands
92
+ let bundledIslands = [];
93
+ if (allDiscoveredIslands.length > 0) {
94
+ bundledIslands = await bundleIslands(allDiscoveredIslands, { outDir, minify });
95
+ }
96
+ // Cleanup compile cache
97
+ await clearCache();
98
+ return {
99
+ pages: processedPages,
100
+ islands: bundledIslands,
101
+ outDir
102
+ };
103
+ }
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import http from 'node:http';
2
+ import { BuildConfig } from '../index.js';
3
+ export interface DevServerOptions extends BuildConfig {
4
+ port?: number;
5
+ host?: string;
6
+ }
7
+ export declare function createDevServer(options?: DevServerOptions): Promise<http.Server>;
8
+ //# sourceMappingURL=dev-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../src/server/dev-server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,OAAO,EAAS,WAAW,EAAE,MAAM,aAAa,CAAC;AAiBjD,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAiG1F"}
@@ -0,0 +1,112 @@
1
+ import http from 'node:http';
2
+ import fs from 'node:fs/promises';
3
+ import { existsSync, watch } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { build } from '../index.js';
6
+ const MIME_TYPES = {
7
+ '.html': 'text/html; charset=utf-8',
8
+ '.js': 'application/javascript; charset=utf-8',
9
+ '.mjs': 'application/javascript; charset=utf-8',
10
+ '.css': 'text/css; charset=utf-8',
11
+ '.json': 'application/json; charset=utf-8',
12
+ '.png': 'image/png',
13
+ '.jpg': 'image/jpeg',
14
+ '.jpeg': 'image/jpeg',
15
+ '.gif': 'image/gif',
16
+ '.svg': 'image/svg+xml',
17
+ '.ico': 'image/x-icon',
18
+ '.woff2': 'font/woff2'
19
+ };
20
+ export async function createDevServer(options = {}) {
21
+ const port = options.port || 3000;
22
+ const host = options.host || 'localhost';
23
+ const root = path.resolve(options.root || process.cwd());
24
+ const outDir = path.resolve(root, options.outDir || 'dist');
25
+ console.log(`[react-html] Performing initial build...`);
26
+ await build({ ...options, minify: false });
27
+ console.log(`[react-html] Build completed.`);
28
+ // Setup file watcher with debounce
29
+ let isRebuilding = false;
30
+ let rebuildTimeout = null;
31
+ const triggerRebuild = () => {
32
+ if (rebuildTimeout)
33
+ clearTimeout(rebuildTimeout);
34
+ rebuildTimeout = setTimeout(async () => {
35
+ if (isRebuilding)
36
+ return;
37
+ isRebuilding = true;
38
+ try {
39
+ console.log(`[react-html] Change detected, rebuilding...`);
40
+ const startTime = Date.now();
41
+ await build({ ...options, minify: false });
42
+ console.log(`[react-html] Rebuild finished in ${Date.now() - startTime}ms`);
43
+ }
44
+ catch (err) {
45
+ console.error(`[react-html] Rebuild error:`, err);
46
+ }
47
+ finally {
48
+ isRebuilding = false;
49
+ }
50
+ }, 100);
51
+ };
52
+ const watchDirs = ['src', 'pages', 'public'].map(d => path.join(root, d)).filter(existsSync);
53
+ for (const dir of watchDirs) {
54
+ try {
55
+ watch(dir, { recursive: true }, (event, filename) => {
56
+ if (filename && !filename.includes('.react-html') && !filename.includes('dist')) {
57
+ triggerRebuild();
58
+ }
59
+ });
60
+ }
61
+ catch {
62
+ // Fallback
63
+ }
64
+ }
65
+ const server = http.createServer(async (req, res) => {
66
+ try {
67
+ const url = new URL(req.url || '/', `http://${host}:${port}`);
68
+ let pathname = decodeURIComponent(url.pathname);
69
+ if (pathname.endsWith('/')) {
70
+ pathname += 'index.html';
71
+ }
72
+ let filePath = path.join(outDir, pathname);
73
+ // If no extension, check if an .html file exists
74
+ if (!path.extname(filePath)) {
75
+ if (existsSync(`${filePath}.html`)) {
76
+ filePath = `${filePath}.html`;
77
+ }
78
+ else if (existsSync(path.join(filePath, 'index.html'))) {
79
+ filePath = path.join(filePath, 'index.html');
80
+ }
81
+ }
82
+ if (!existsSync(filePath)) {
83
+ res.writeHead(404, { 'Content-Type': 'text/html' });
84
+ res.end(`<h1>404 Not Found</h1><p>File not found: ${pathname}</p>`);
85
+ return;
86
+ }
87
+ const stat = await fs.stat(filePath);
88
+ if (stat.isDirectory()) {
89
+ filePath = path.join(filePath, 'index.html');
90
+ }
91
+ const ext = path.extname(filePath).toLowerCase();
92
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
93
+ const content = await fs.readFile(filePath);
94
+ res.writeHead(200, {
95
+ 'Content-Type': contentType,
96
+ 'Content-Length': content.length
97
+ });
98
+ res.end(content);
99
+ }
100
+ catch (err) {
101
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
102
+ res.end(`500 Internal Server Error: ${err?.message || err}`);
103
+ }
104
+ });
105
+ return new Promise((resolve) => {
106
+ server.listen(port, host, () => {
107
+ console.log(`[react-html] Dev server running at http://${host}:${port}/`);
108
+ resolve(server);
109
+ });
110
+ });
111
+ }
112
+ //# sourceMappingURL=dev-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server.js","sourceRoot":"","sources":["../../src/server/dev-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export interface EngineOptions {
3
+ /** Project root directory (default: process.cwd()) */
4
+ root?: string;
5
+ /** Source pages directory (default: 'src/pages' or 'pages') */
6
+ pagesDir?: string;
7
+ /** Production build output directory (default: 'dist') */
8
+ distDir?: string;
9
+ /** Static public directory (default: 'public') */
10
+ publicDir?: string;
11
+ /** Force production mode. If omitted, checks process.env.NODE_ENV === 'production' */
12
+ isProduction?: boolean;
13
+ /** Base asset URL prefix (default: '/') */
14
+ basePath?: string;
15
+ }
16
+ export declare class ReactHtmlEngine {
17
+ readonly root: string;
18
+ readonly pagesDir: string;
19
+ readonly distDir: string;
20
+ readonly publicDir: string;
21
+ readonly isProduction: boolean;
22
+ readonly basePath: string;
23
+ constructor(options?: EngineOptions);
24
+ /**
25
+ * Render a page by name or route.
26
+ * - In production: checks for pre-built static HTML in dist/ first.
27
+ * - In development: recompiles the TSX page on the fly and bundles any needed reactive islands.
28
+ */
29
+ render(pageRoute: string, props?: Record<string, unknown>, metadata?: Record<string, unknown>): Promise<string>;
30
+ /**
31
+ * Universal Connect / Express / Node.js HTTP middleware.
32
+ * Handles page routes and static assets according to environment mode.
33
+ */
34
+ middleware: (req: IncomingMessage, res: ServerResponse, next?: () => void) => Promise<void>;
35
+ /**
36
+ * Standalone HTTP request handler suitable for http.createServer(engine.handle)
37
+ */
38
+ handle: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
39
+ private resolveSourcePage;
40
+ private tryServeStaticAsset;
41
+ }
42
+ /**
43
+ * Factory function to create a new ReactHtmlEngine instance
44
+ */
45
+ export declare function createEngine(options?: EngineOptions): ReactHtmlEngine;
46
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/server/engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMjE,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAiBD,qBAAa,eAAe;IAC1B,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,YAAY,EAAE,OAAO,CAAC;IACtC,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,OAAO,GAAE,aAAkB;IA6BvC;;;;OAIG;IACU,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,MAAM,CAAC;IAgDlB;;;OAGG;IACI,UAAU,GACf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,OAAO,MAAM,IAAI,KAChB,OAAO,CAAC,IAAI,CAAC,CAoEd;IAEF;;OAEG;IACI,MAAM,GAAI,KAAK,eAAe,EAAE,KAAK,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CAExE;YAEY,iBAAiB;YAejB,mBAAmB;CA4BlC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,eAAe,CAEzE"}