@bouygues-telecom/staticjs 0.1.11 → 0.1.14
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.
- package/_build/helpers/cachePages.d.ts +15 -0
- package/_build/helpers/cachePages.js +162 -0
- package/_build/helpers/createPage.d.ts +18 -0
- package/_build/helpers/createPage.js +41 -0
- package/_build/helpers/layoutDiscovery.d.ts +9 -0
- package/_build/helpers/layoutDiscovery.js +39 -0
- package/_build/helpers/readPages.d.ts +3 -0
- package/_build/helpers/readPages.js +24 -0
- package/_build/helpers/renderPageRuntime.d.ts +13 -0
- package/_build/helpers/renderPageRuntime.js +222 -0
- package/_build/scripts/build-html.d.ts +1 -0
- package/_build/scripts/build-html.js +138 -0
- package/_build/scripts/cli.d.ts +6 -0
- package/_build/scripts/cli.js +119 -0
- package/_build/scripts/create-static-app.d.ts +2 -0
- package/{dist → _build}/scripts/create-static-app.js +0 -0
- package/_build/scripts/generate-test-multiapps.d.ts +6 -0
- package/_build/scripts/generate-test-multiapps.js +79 -0
- package/_build/server/config/index.d.ts +20 -0
- package/_build/server/config/index.js +25 -0
- package/_build/server/config/vite.config.d.ts +2 -0
- package/_build/server/config/vite.config.js +31 -0
- package/_build/server/config/vite.plugin.d.ts +11 -0
- package/_build/server/config/vite.plugin.js +75 -0
- package/_build/server/index.d.ts +22 -0
- package/_build/server/index.js +124 -0
- package/_build/server/middleware/errorHandling.d.ts +22 -0
- package/_build/server/middleware/errorHandling.js +48 -0
- package/_build/server/middleware/hotReload.d.ts +31 -0
- package/_build/server/middleware/hotReload.js +152 -0
- package/_build/server/middleware/logging.d.ts +13 -0
- package/_build/server/middleware/logging.js +23 -0
- package/_build/server/middleware/parsing.d.ts +9 -0
- package/_build/server/middleware/parsing.js +21 -0
- package/_build/server/middleware/performance.d.ts +13 -0
- package/_build/server/middleware/performance.js +26 -0
- package/_build/server/middleware/rateLimiting.d.ts +17 -0
- package/_build/server/middleware/rateLimiting.js +38 -0
- package/_build/server/middleware/runtime.d.ts +23 -0
- package/_build/server/middleware/runtime.js +186 -0
- package/_build/server/middleware/security.d.ts +24 -0
- package/_build/server/middleware/security.js +39 -0
- package/_build/server/middleware/static.d.ts +9 -0
- package/_build/server/middleware/static.js +63 -0
- package/_build/server/routes/api.d.ts +23 -0
- package/_build/server/routes/api.js +124 -0
- package/_build/server/scripts/revalidate.d.ts +2 -0
- package/{dist → _build/server}/scripts/revalidate.js +3 -3
- package/_build/server/utils/fileWatcher.d.ts +27 -0
- package/_build/server/utils/fileWatcher.js +194 -0
- package/_build/server/utils/startup.d.ts +18 -0
- package/_build/server/utils/startup.js +93 -0
- package/_build/server/utils/vite.d.ts +18 -0
- package/_build/server/utils/vite.js +61 -0
- package/_build/server/utils/websocket.d.ts +26 -0
- package/_build/server/utils/websocket.js +140 -0
- package/package.json +28 -16
- package/LICENSE +0 -190
- package/README.md +0 -158
- package/dist/config/vite.plugin.js +0 -80
- package/dist/helpers/cachePages.js +0 -26
- package/dist/helpers/createPage.js +0 -23
- package/dist/helpers/readPages.js +0 -19
- package/dist/scripts/build-html.js +0 -82
- package/dist/scripts/cli.js +0 -36
- package/dist/scripts/generate-test-multiapps.js +0 -51
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load cache entries with error handling and auto-generation
|
|
3
|
+
* @param projectDir - The template directory path (e.g., "templates/react")
|
|
4
|
+
* @param verbose
|
|
5
|
+
* @returns The loaded or generated cache entries object
|
|
6
|
+
*/
|
|
7
|
+
export declare const loadCacheEntries: (projectDir: string, verbose?: boolean) => any;
|
|
8
|
+
/**
|
|
9
|
+
* CLI wrapper that uses the modern API functions
|
|
10
|
+
* @param templateDir - Template directory (defaults to current directory for backward compatibility)
|
|
11
|
+
* @param specificFiles - Optional array of specific .tsx files to process
|
|
12
|
+
*/
|
|
13
|
+
export declare const runCli: (templateDir?: string, specificFiles?: string[]) => {
|
|
14
|
+
[key: string]: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { readPages } from "./readPages.js";
|
|
4
|
+
import { CONFIG } from "../server/config/index.js";
|
|
5
|
+
const generateExcludedFiles = (entries) => {
|
|
6
|
+
const excludedFiles = [];
|
|
7
|
+
Object.entries(entries).forEach(([name, path]) => {
|
|
8
|
+
const content = fs.readFileSync(path, "utf8");
|
|
9
|
+
const firstLine = content.split("\n")[0];
|
|
10
|
+
if (firstLine.includes("no scripts")) {
|
|
11
|
+
delete entries[name];
|
|
12
|
+
excludedFiles.push(name);
|
|
13
|
+
// File excluded from build due to "no scripts" directive
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
const cacheDir = path.resolve(process.cwd(), CONFIG.BUILD_DIR, "cache");
|
|
18
|
+
const excludedFilePath = path.resolve(cacheDir, "excludedFiles.json");
|
|
19
|
+
if (!fs.existsSync(cacheDir)) {
|
|
20
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(excludedFilePath, JSON.stringify(excludedFiles, null, 2), "utf8");
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error("Error generating excluded files:", error);
|
|
26
|
+
throw error; // Re-throw to handle it in the caller
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Generate cache entries for a given template directory
|
|
31
|
+
* @param projectDir - The template directory path (e.g., "templates/react")
|
|
32
|
+
* @param verbose
|
|
33
|
+
* @returns The generated cache entries object
|
|
34
|
+
*/
|
|
35
|
+
const generateCacheEntries = (projectDir, verbose = false) => {
|
|
36
|
+
const pagesDir = path.resolve(projectDir, "src/pages");
|
|
37
|
+
const cacheDir = path.resolve(projectDir, CONFIG.BUILD_DIR, "cache");
|
|
38
|
+
const cacheFilePath = path.resolve(cacheDir, "pagesCache.json");
|
|
39
|
+
// Generating pages cache (silent unless error)
|
|
40
|
+
// Check if pages directory exists
|
|
41
|
+
if (!fs.existsSync(pagesDir)) {
|
|
42
|
+
if (verbose) {
|
|
43
|
+
console.warn(`Pages directory not found: ${pagesDir}`);
|
|
44
|
+
}
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
// Scan for page files using existing helper
|
|
48
|
+
const entries = readPages(pagesDir);
|
|
49
|
+
// Create cache directory if it doesn't exist
|
|
50
|
+
if (!fs.existsSync(cacheDir)) {
|
|
51
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
52
|
+
if (verbose) {
|
|
53
|
+
console.log(` Created cache directory: ${cacheDir}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Write cache file
|
|
57
|
+
fs.writeFileSync(cacheFilePath, JSON.stringify(entries, null, 2), "utf8");
|
|
58
|
+
generateExcludedFiles(entries);
|
|
59
|
+
if (verbose) {
|
|
60
|
+
console.log(` Generated cache file: ${cacheFilePath}`);
|
|
61
|
+
console.log(` Found ${Object.keys(entries).length} page(s)\n`);
|
|
62
|
+
}
|
|
63
|
+
return entries;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Load cache entries with error handling and auto-generation
|
|
67
|
+
* @param projectDir - The template directory path (e.g., "templates/react")
|
|
68
|
+
* @param verbose
|
|
69
|
+
* @returns The loaded or generated cache entries object
|
|
70
|
+
*/
|
|
71
|
+
export const loadCacheEntries = (projectDir, verbose = false) => {
|
|
72
|
+
const cacheFilePath = path.resolve(projectDir, CONFIG.BUILD_DIR, "cache/pagesCache.json");
|
|
73
|
+
try {
|
|
74
|
+
if (!fs.existsSync(cacheFilePath)) {
|
|
75
|
+
if (verbose) {
|
|
76
|
+
console.log('\n📝 Cache file not found, generating automatically...');
|
|
77
|
+
}
|
|
78
|
+
return generateCacheEntries(projectDir, verbose);
|
|
79
|
+
}
|
|
80
|
+
const entries = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8'));
|
|
81
|
+
if (!entries || typeof entries !== 'object') {
|
|
82
|
+
if (verbose) {
|
|
83
|
+
console.log('\n📝 Invalid cache file format, regenerating...');
|
|
84
|
+
}
|
|
85
|
+
return generateCacheEntries(projectDir, verbose);
|
|
86
|
+
}
|
|
87
|
+
if (verbose) {
|
|
88
|
+
console.log(`\n✅ Loaded ${Object.keys(entries).length} cached page(s)`);
|
|
89
|
+
}
|
|
90
|
+
return entries;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
94
|
+
console.error('\n❌ StaticJS Cache Error:');
|
|
95
|
+
console.error(` ${errorMessage}`);
|
|
96
|
+
console.log('\n📝 Attempting to generate cache automatically...');
|
|
97
|
+
try {
|
|
98
|
+
return generateCacheEntries(projectDir, verbose);
|
|
99
|
+
}
|
|
100
|
+
catch (generateError) {
|
|
101
|
+
const generateErrorMessage = generateError instanceof Error ? generateError.message : String(generateError);
|
|
102
|
+
console.error('\n❌ Failed to generate cache automatically:');
|
|
103
|
+
console.error(` ${generateErrorMessage}`);
|
|
104
|
+
console.error('\n💡 To fix this issue:');
|
|
105
|
+
console.error(' 1. Run: npm run build');
|
|
106
|
+
console.error(' 2. Or run: npm run dev (will auto-build if needed)');
|
|
107
|
+
console.error(' 3. Or run: npm run validate-setup\n');
|
|
108
|
+
// Return empty object to prevent Vite from crashing
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Process CLI arguments to generate specific page entries
|
|
115
|
+
* @param args - Command line arguments
|
|
116
|
+
* @param pagesDir - Pages directory path
|
|
117
|
+
* @returns Processed entries object
|
|
118
|
+
*/
|
|
119
|
+
const processCliArgs = (args, pagesDir) => {
|
|
120
|
+
return args
|
|
121
|
+
.filter((arg) => arg.endsWith(".tsx"))
|
|
122
|
+
.reduce((obj, tsxFile) => {
|
|
123
|
+
console.log(`Processing arg: ${tsxFile}`);
|
|
124
|
+
const relativePathWithoutExtension = tsxFile.replace(/\.tsx$/, "");
|
|
125
|
+
const fullPath = path.resolve(pagesDir, tsxFile);
|
|
126
|
+
obj[relativePathWithoutExtension] = fullPath;
|
|
127
|
+
return obj;
|
|
128
|
+
}, {});
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* CLI wrapper that uses the modern API functions
|
|
132
|
+
* @param templateDir - Template directory (defaults to current directory for backward compatibility)
|
|
133
|
+
* @param specificFiles - Optional array of specific .tsx files to process
|
|
134
|
+
*/
|
|
135
|
+
export const runCli = (templateDir = ".", specificFiles) => {
|
|
136
|
+
const args = specificFiles || process.argv.slice(2);
|
|
137
|
+
if (args.length > 0) {
|
|
138
|
+
// Handle specific files by temporarily overriding readPages behavior
|
|
139
|
+
const pagesDir = path.resolve(process.cwd(), templateDir, "src/pages");
|
|
140
|
+
const entries = processCliArgs(args, pagesDir);
|
|
141
|
+
// Use generateCacheEntries but with custom entries
|
|
142
|
+
const cacheDir = path.resolve(process.cwd(), templateDir, CONFIG.BUILD_DIR, "cache");
|
|
143
|
+
const cacheFilePath = path.resolve(cacheDir, "pagesCache.json");
|
|
144
|
+
if (!fs.existsSync(cacheDir)) {
|
|
145
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
fs.writeFileSync(cacheFilePath, JSON.stringify(entries, null, 2), "utf8");
|
|
148
|
+
console.log("Pages cached successfully.");
|
|
149
|
+
return entries;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Use the modern API for full directory scanning
|
|
153
|
+
return generateCacheEntries(templateDir, true);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
// Legacy CLI support - maintain backward compatibility
|
|
157
|
+
// Only run CLI logic if this file is executed directly (not imported)
|
|
158
|
+
// Check if this is the main module using import.meta.url
|
|
159
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
160
|
+
if (isMainModule) {
|
|
161
|
+
runCli();
|
|
162
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface IcreatePage {
|
|
3
|
+
data: any;
|
|
4
|
+
AppComponent: React.FC<{
|
|
5
|
+
Component: React.FC;
|
|
6
|
+
props: {};
|
|
7
|
+
pageData?: any;
|
|
8
|
+
}>;
|
|
9
|
+
PageComponent: () => React.JSX.Element;
|
|
10
|
+
initialDatasId: string;
|
|
11
|
+
rootId: string;
|
|
12
|
+
pageName: string;
|
|
13
|
+
JSfileName: string | false;
|
|
14
|
+
returnHtml?: boolean;
|
|
15
|
+
pageData?: any;
|
|
16
|
+
}
|
|
17
|
+
export declare const createPage: ({ data, AppComponent, PageComponent, initialDatasId, rootId, pageName, JSfileName, returnHtml, pageData, }: IcreatePage) => Promise<string | void>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import ReactDOMServer from "react-dom/server";
|
|
5
|
+
import { CONFIG } from "../server/config/index.js";
|
|
6
|
+
export const createPage = async ({ data, AppComponent, PageComponent, initialDatasId, rootId, pageName, JSfileName, returnHtml = false, // Default to false for backward compatibility
|
|
7
|
+
pageData = {}, // Default to empty object
|
|
8
|
+
}) => {
|
|
9
|
+
const template = `{{html}}
|
|
10
|
+
${data ? `<script id=initial-data-{{initialDatasId}} type="application/json">${JSON.stringify(data)}</script>` : ""}
|
|
11
|
+
${JSfileName ? `<script type="module" src="{{scriptPath}}"></script>` : ""}
|
|
12
|
+
`;
|
|
13
|
+
// Create a wrapper component that adds the app div around the page component
|
|
14
|
+
const PageWithAppDiv = (props) => {
|
|
15
|
+
return React.createElement('div', { id: `app-${rootId}` }, React.createElement(PageComponent, props));
|
|
16
|
+
};
|
|
17
|
+
const component = React.createElement(AppComponent, {
|
|
18
|
+
Component: PageWithAppDiv,
|
|
19
|
+
props: { data },
|
|
20
|
+
pageData, // Pass pageData to AppComponent
|
|
21
|
+
});
|
|
22
|
+
const htmlContent = template
|
|
23
|
+
.replace("{{initialDatasId}}", initialDatasId)
|
|
24
|
+
.replace("{{html}}", ReactDOMServer.renderToString(component))
|
|
25
|
+
.replace("{{scriptPath}}", `/${pageName}.js`);
|
|
26
|
+
// Return HTML string for runtime rendering or write to file for build time
|
|
27
|
+
if (returnHtml) {
|
|
28
|
+
return htmlContent;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// If page name contains a slash, create directories
|
|
32
|
+
const pageNameParts = pageName.split("/");
|
|
33
|
+
if (pageNameParts.length > 1) {
|
|
34
|
+
const dirPath = path.join(CONFIG.PROJECT_ROOT, CONFIG.BUILD_DIR, ...pageNameParts.slice(0, -1));
|
|
35
|
+
if (!fs.existsSync(dirPath)) {
|
|
36
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
fs.writeFileSync(path.join(CONFIG.PROJECT_ROOT, CONFIG.BUILD_DIR, `${pageName}.html`), htmlContent);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovers the closest layout.tsx file by walking up the directory tree
|
|
3
|
+
* from the given page path
|
|
4
|
+
*/
|
|
5
|
+
export declare function findClosestLayout(pagePath: string, rootDir: string): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Gets the relative path from rootDir for layout identification
|
|
8
|
+
*/
|
|
9
|
+
export declare function getLayoutIdentifier(layoutPath: string, rootDir: string): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Discovers the closest layout.tsx file by walking up the directory tree
|
|
5
|
+
* from the given page path
|
|
6
|
+
*/
|
|
7
|
+
export function findClosestLayout(pagePath, rootDir) {
|
|
8
|
+
// Get the directory containing the page
|
|
9
|
+
const pageDir = path.dirname(pagePath);
|
|
10
|
+
// Start from the page's directory and walk up
|
|
11
|
+
let currentDir = pageDir;
|
|
12
|
+
while (true) {
|
|
13
|
+
// Check if layout.tsx exists in current directory
|
|
14
|
+
const layoutPath = path.join(currentDir, "layout.tsx");
|
|
15
|
+
if (fs.existsSync(layoutPath)) {
|
|
16
|
+
return layoutPath;
|
|
17
|
+
}
|
|
18
|
+
// Get parent directory
|
|
19
|
+
const parentDir = path.dirname(currentDir);
|
|
20
|
+
// If we've reached the root directory or can't go up further, stop
|
|
21
|
+
if (parentDir === currentDir || !currentDir.startsWith(rootDir)) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
currentDir = parentDir;
|
|
25
|
+
}
|
|
26
|
+
// Fallback to global layout if no layout.tsx found in the hierarchy
|
|
27
|
+
const globalLayoutPath = path.join(rootDir, "layout.tsx");
|
|
28
|
+
if (fs.existsSync(globalLayoutPath)) {
|
|
29
|
+
return globalLayoutPath;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Gets the relative path from rootDir for layout identification
|
|
35
|
+
*/
|
|
36
|
+
export function getLayoutIdentifier(layoutPath, rootDir) {
|
|
37
|
+
const relativePath = path.relative(rootDir, layoutPath);
|
|
38
|
+
return relativePath.replace(/\.tsx$/, "");
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export function readPages(dir, baseDir = dir) {
|
|
4
|
+
let result = {};
|
|
5
|
+
const files = fs.readdirSync(dir);
|
|
6
|
+
for (const file of files) {
|
|
7
|
+
const fullPath = path.join(dir, file);
|
|
8
|
+
const stat = fs.statSync(fullPath);
|
|
9
|
+
if (stat.isDirectory()) {
|
|
10
|
+
// Check if this directory contains an index.tsx file
|
|
11
|
+
const indexPath = path.join(fullPath, "index.tsx");
|
|
12
|
+
if (fs.existsSync(indexPath)) {
|
|
13
|
+
// Create route path from folder structure
|
|
14
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
15
|
+
result[relPath] = indexPath;
|
|
16
|
+
}
|
|
17
|
+
// Continue recursively searching for more pages
|
|
18
|
+
const nested = readPages(fullPath, baseDir);
|
|
19
|
+
result = { ...result, ...nested };
|
|
20
|
+
}
|
|
21
|
+
// Note: Direct .tsx files are no longer supported - only folder-based routes with index.tsx
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime page rendering function that uses the same logic as build-html.ts
|
|
3
|
+
* but returns HTML string instead of writing to file
|
|
4
|
+
*/
|
|
5
|
+
export declare function renderPageRuntime(requestPath: string, params?: {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}): Promise<string | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Get all available pages for development mode
|
|
10
|
+
*/
|
|
11
|
+
export declare function getAvailablePagesRuntime(): {
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { createPage } from "./createPage.js";
|
|
7
|
+
import { readPages } from "./readPages.js";
|
|
8
|
+
import { CONFIG } from "../server/config/index.js";
|
|
9
|
+
import { findClosestLayout } from "./layoutDiscovery.js";
|
|
10
|
+
const rootDir = path.resolve(process.cwd(), "./src");
|
|
11
|
+
async function loadJson(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
14
|
+
return JSON.parse(data);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Return empty object if file doesn't exist (for development mode)
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runtime page rendering function that uses the same logic as build-html.ts
|
|
23
|
+
* but returns HTML string instead of writing to file
|
|
24
|
+
*/
|
|
25
|
+
export async function renderPageRuntime(requestPath, params) {
|
|
26
|
+
try {
|
|
27
|
+
// Load excluded files for "no scripts" functionality
|
|
28
|
+
const excludedJSFiles = await loadJson(path.join(process.cwd(), `./${CONFIG.BUILD_DIR}/cache/excludedFiles.json`));
|
|
29
|
+
// Get available pages using readPages helper
|
|
30
|
+
const pagesDir = path.resolve(process.cwd(), "src/pages");
|
|
31
|
+
const pages = readPages(pagesDir);
|
|
32
|
+
// Find the matching page
|
|
33
|
+
let matchedPage = null;
|
|
34
|
+
let matchedParams = {};
|
|
35
|
+
// Handle root path
|
|
36
|
+
if (requestPath === "/" || requestPath === "") {
|
|
37
|
+
if (pages["index"]) {
|
|
38
|
+
matchedPage = { path: pages["index"], pageName: "index" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Remove leading slash and try exact match first
|
|
43
|
+
const cleanPath = requestPath.replace(/^\//, "");
|
|
44
|
+
// Try exact match first
|
|
45
|
+
if (pages[cleanPath]) {
|
|
46
|
+
matchedPage = { path: pages[cleanPath], pageName: cleanPath };
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Try to match with different prefixes (for flexibility)
|
|
50
|
+
const possiblePaths = [
|
|
51
|
+
cleanPath,
|
|
52
|
+
`partials/${cleanPath}`,
|
|
53
|
+
cleanPath.replace(/^partials\//, "")
|
|
54
|
+
];
|
|
55
|
+
for (const testPath of possiblePaths) {
|
|
56
|
+
if (pages[testPath]) {
|
|
57
|
+
matchedPage = { path: pages[testPath], pageName: testPath };
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// If still no match, try dynamic routes
|
|
62
|
+
if (!matchedPage) {
|
|
63
|
+
for (const [pageName, pagePath] of Object.entries(pages)) {
|
|
64
|
+
if (pageName.includes("[") && pageName.includes("]")) {
|
|
65
|
+
// Extract the dynamic parameter name
|
|
66
|
+
const paramMatch = pageName.match(/\[([^\]]+)\]/);
|
|
67
|
+
if (paramMatch) {
|
|
68
|
+
const paramName = paramMatch[1];
|
|
69
|
+
// Try matching against multiple possible paths
|
|
70
|
+
for (const testPath of possiblePaths) {
|
|
71
|
+
// Create a regex pattern from the page name
|
|
72
|
+
const regexPattern = pageName.replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
73
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
74
|
+
const match = testPath.match(regex);
|
|
75
|
+
if (match) {
|
|
76
|
+
// Extract the parameter value from the matched groups
|
|
77
|
+
const paramValue = match[1];
|
|
78
|
+
if (paramValue) {
|
|
79
|
+
matchedPage = { path: pagePath, pageName: testPath };
|
|
80
|
+
matchedParams[paramName] = paramValue;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (matchedPage)
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!matchedPage) {
|
|
94
|
+
return null; // Page not found
|
|
95
|
+
}
|
|
96
|
+
return await processPageRuntime(matchedPage, excludedJSFiles, matchedParams);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error("Error in renderPageRuntime:", error);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Process a single page for runtime rendering (adapted from build-html.ts processPage)
|
|
105
|
+
*/
|
|
106
|
+
async function processPageRuntime(page, excludedJSFiles, params = {}) {
|
|
107
|
+
let data;
|
|
108
|
+
const absolutePath = page.path;
|
|
109
|
+
// Add cache busting to avoid module caching issues in development
|
|
110
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
111
|
+
try {
|
|
112
|
+
const pageModule = await import(`${absolutePath}${cacheBuster}`);
|
|
113
|
+
// Load page data.json if it exists
|
|
114
|
+
const pageDir = path.dirname(absolutePath);
|
|
115
|
+
const dataJsonPath = path.join(pageDir, 'data.json');
|
|
116
|
+
let pageData = {};
|
|
117
|
+
try {
|
|
118
|
+
const dataContent = await fs.readFile(dataJsonPath, 'utf-8');
|
|
119
|
+
pageData = JSON.parse(dataContent);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// data.json doesn't exist, use empty object
|
|
123
|
+
}
|
|
124
|
+
// Discover the closest layout for this page
|
|
125
|
+
const layoutPath = findClosestLayout(absolutePath, rootDir);
|
|
126
|
+
if (!layoutPath) {
|
|
127
|
+
throw new Error(`No layout found for page ${page.pageName}`);
|
|
128
|
+
}
|
|
129
|
+
const layoutModule = await import(`${layoutPath}${cacheBuster}`);
|
|
130
|
+
const appModule = await import(`${rootDir}/app.tsx${cacheBuster}`);
|
|
131
|
+
const fileName = path.basename(page.path, path.extname(page.path));
|
|
132
|
+
// Create a dynamic App component that uses the discovered layout
|
|
133
|
+
const LayoutComponent = layoutModule.Layout;
|
|
134
|
+
if (!LayoutComponent) {
|
|
135
|
+
throw new Error(`Layout component not found in ${layoutPath}. Make sure it exports 'Layout'.`);
|
|
136
|
+
}
|
|
137
|
+
// Create a wrapper App component that uses the actual App component
|
|
138
|
+
const AppComponent = ({ Component, props }) => {
|
|
139
|
+
// Create a layout wrapper that accepts pageData
|
|
140
|
+
const LayoutWrapper = ({ children }) => {
|
|
141
|
+
return React.createElement(LayoutComponent, { pageData, children });
|
|
142
|
+
};
|
|
143
|
+
// Use the actual App component and wrap it with our layout
|
|
144
|
+
return React.createElement(LayoutWrapper, {
|
|
145
|
+
children: React.createElement(appModule.App, { Component, props, pageData })
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
const PageComponent = pageModule.default;
|
|
149
|
+
const getStaticProps = pageModule?.getStaticProps;
|
|
150
|
+
const getStaticPaths = pageModule?.getStaticPaths;
|
|
151
|
+
const injectJS = !excludedJSFiles.includes(page.pageName);
|
|
152
|
+
const rootId = crypto
|
|
153
|
+
.createHash("sha256")
|
|
154
|
+
.update(`app-${absolutePath}`)
|
|
155
|
+
.digest("hex")
|
|
156
|
+
.slice(0, 10);
|
|
157
|
+
const initialDatasId = crypto
|
|
158
|
+
.createHash("sha256")
|
|
159
|
+
.update(`initial-data-${absolutePath}`)
|
|
160
|
+
.digest("hex")
|
|
161
|
+
.slice(0, 10);
|
|
162
|
+
if (!PageComponent) {
|
|
163
|
+
throw new Error(`Failed to import PageComponent from ${page.pageName}.tsx`);
|
|
164
|
+
}
|
|
165
|
+
// Handle getStaticProps with or without dynamic params
|
|
166
|
+
if (getStaticProps) {
|
|
167
|
+
if (Object.keys(params).length > 0) {
|
|
168
|
+
// Dynamic route with params
|
|
169
|
+
const { props } = await getStaticProps({ params });
|
|
170
|
+
data = props.data;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Static route
|
|
174
|
+
const { props } = await getStaticProps();
|
|
175
|
+
data = props.data;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Generate HTML using createPage helper with returnHtml flag
|
|
179
|
+
// Use dynamic import to load createPage from the template directory
|
|
180
|
+
let htmlContent;
|
|
181
|
+
try {
|
|
182
|
+
// Try to import createPage from the template's helpers directory
|
|
183
|
+
const templateCreatePagePath = path.resolve(process.cwd(), "../../helpers/createPage.js");
|
|
184
|
+
const { createPage: templateCreatePage } = await import(templateCreatePagePath);
|
|
185
|
+
htmlContent = templateCreatePage({
|
|
186
|
+
data,
|
|
187
|
+
AppComponent,
|
|
188
|
+
PageComponent,
|
|
189
|
+
initialDatasId,
|
|
190
|
+
rootId,
|
|
191
|
+
pageName: page.pageName,
|
|
192
|
+
JSfileName: injectJS && fileName,
|
|
193
|
+
returnHtml: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
// Fallback to server's createPage
|
|
198
|
+
htmlContent = await createPage({
|
|
199
|
+
data,
|
|
200
|
+
AppComponent,
|
|
201
|
+
PageComponent,
|
|
202
|
+
initialDatasId,
|
|
203
|
+
rootId,
|
|
204
|
+
pageName: page.pageName,
|
|
205
|
+
JSfileName: injectJS && fileName,
|
|
206
|
+
returnHtml: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return htmlContent;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error(`[DEBUG] Error in processPageRuntime:`, error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get all available pages for development mode
|
|
218
|
+
*/
|
|
219
|
+
export function getAvailablePagesRuntime() {
|
|
220
|
+
const pagesDir = path.resolve(process.cwd(), "src/pages");
|
|
221
|
+
return readPages(pagesDir);
|
|
222
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|