@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.
Files changed (66) hide show
  1. package/_build/helpers/cachePages.d.ts +15 -0
  2. package/_build/helpers/cachePages.js +162 -0
  3. package/_build/helpers/createPage.d.ts +18 -0
  4. package/_build/helpers/createPage.js +41 -0
  5. package/_build/helpers/layoutDiscovery.d.ts +9 -0
  6. package/_build/helpers/layoutDiscovery.js +39 -0
  7. package/_build/helpers/readPages.d.ts +3 -0
  8. package/_build/helpers/readPages.js +24 -0
  9. package/_build/helpers/renderPageRuntime.d.ts +13 -0
  10. package/_build/helpers/renderPageRuntime.js +222 -0
  11. package/_build/scripts/build-html.d.ts +1 -0
  12. package/_build/scripts/build-html.js +138 -0
  13. package/_build/scripts/cli.d.ts +6 -0
  14. package/_build/scripts/cli.js +119 -0
  15. package/_build/scripts/create-static-app.d.ts +2 -0
  16. package/{dist → _build}/scripts/create-static-app.js +0 -0
  17. package/_build/scripts/generate-test-multiapps.d.ts +6 -0
  18. package/_build/scripts/generate-test-multiapps.js +79 -0
  19. package/_build/server/config/index.d.ts +20 -0
  20. package/_build/server/config/index.js +25 -0
  21. package/_build/server/config/vite.config.d.ts +2 -0
  22. package/_build/server/config/vite.config.js +31 -0
  23. package/_build/server/config/vite.plugin.d.ts +11 -0
  24. package/_build/server/config/vite.plugin.js +75 -0
  25. package/_build/server/index.d.ts +22 -0
  26. package/_build/server/index.js +124 -0
  27. package/_build/server/middleware/errorHandling.d.ts +22 -0
  28. package/_build/server/middleware/errorHandling.js +48 -0
  29. package/_build/server/middleware/hotReload.d.ts +31 -0
  30. package/_build/server/middleware/hotReload.js +152 -0
  31. package/_build/server/middleware/logging.d.ts +13 -0
  32. package/_build/server/middleware/logging.js +23 -0
  33. package/_build/server/middleware/parsing.d.ts +9 -0
  34. package/_build/server/middleware/parsing.js +21 -0
  35. package/_build/server/middleware/performance.d.ts +13 -0
  36. package/_build/server/middleware/performance.js +26 -0
  37. package/_build/server/middleware/rateLimiting.d.ts +17 -0
  38. package/_build/server/middleware/rateLimiting.js +38 -0
  39. package/_build/server/middleware/runtime.d.ts +23 -0
  40. package/_build/server/middleware/runtime.js +186 -0
  41. package/_build/server/middleware/security.d.ts +24 -0
  42. package/_build/server/middleware/security.js +39 -0
  43. package/_build/server/middleware/static.d.ts +9 -0
  44. package/_build/server/middleware/static.js +63 -0
  45. package/_build/server/routes/api.d.ts +23 -0
  46. package/_build/server/routes/api.js +124 -0
  47. package/_build/server/scripts/revalidate.d.ts +2 -0
  48. package/{dist → _build/server}/scripts/revalidate.js +3 -3
  49. package/_build/server/utils/fileWatcher.d.ts +27 -0
  50. package/_build/server/utils/fileWatcher.js +194 -0
  51. package/_build/server/utils/startup.d.ts +18 -0
  52. package/_build/server/utils/startup.js +93 -0
  53. package/_build/server/utils/vite.d.ts +18 -0
  54. package/_build/server/utils/vite.js +61 -0
  55. package/_build/server/utils/websocket.d.ts +26 -0
  56. package/_build/server/utils/websocket.js +140 -0
  57. package/package.json +28 -16
  58. package/LICENSE +0 -190
  59. package/README.md +0 -158
  60. package/dist/config/vite.plugin.js +0 -80
  61. package/dist/helpers/cachePages.js +0 -26
  62. package/dist/helpers/createPage.js +0 -23
  63. package/dist/helpers/readPages.js +0 -19
  64. package/dist/scripts/build-html.js +0 -82
  65. package/dist/scripts/cli.js +0 -36
  66. package/dist/scripts/generate-test-multiapps.js +0 -51
@@ -0,0 +1,138 @@
1
+ import fs from "fs/promises";
2
+ import crypto from "node:crypto";
3
+ import path from "path";
4
+ import React from "react";
5
+ import { createPage } from "../helpers/createPage.js";
6
+ import { CONFIG } from "../server/config/index.js";
7
+ import { loadCacheEntries } from "../helpers/cachePages.js";
8
+ import { findClosestLayout } from "../helpers/layoutDiscovery.js";
9
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
10
+ async function loadJson(filePath) {
11
+ const data = await fs.readFile(filePath, "utf-8");
12
+ return JSON.parse(data);
13
+ }
14
+ async function main() {
15
+ loadCacheEntries(CONFIG.PROJECT_ROOT);
16
+ const excludedJSFiles = await loadJson(path.join(CONFIG.PROJECT_ROOT, CONFIG.BUILD_DIR, "cache/excludedFiles.json"));
17
+ const files = await loadJson(path.join(CONFIG.PROJECT_ROOT, CONFIG.BUILD_DIR, "cache/pagesCache.json"));
18
+ const processPage = async (page) => {
19
+ try {
20
+ let data;
21
+ const absolutePath = page.path;
22
+ const pageModule = await import(absolutePath);
23
+ const appModule = await import(`${CONFIG.PROJECT_ROOT}/src/app.tsx`);
24
+ const fileName = path.basename(page.path, path.extname(page.path));
25
+ // Load page data.json if it exists
26
+ const pageDir = path.dirname(absolutePath);
27
+ const dataJsonPath = path.join(pageDir, 'data.json');
28
+ let pageData = {};
29
+ try {
30
+ const dataContent = await fs.readFile(dataJsonPath, 'utf-8');
31
+ pageData = JSON.parse(dataContent);
32
+ }
33
+ catch (error) {
34
+ // data.json doesn't exist, use empty object
35
+ }
36
+ // Discover the closest layout for this page
37
+ const rootDir = path.resolve(CONFIG.PROJECT_ROOT, "./src");
38
+ const layoutPath = findClosestLayout(absolutePath, rootDir);
39
+ if (!layoutPath) {
40
+ throw new Error(`No layout found for page ${page.pageName}`);
41
+ }
42
+ const layoutModule = await import(layoutPath);
43
+ const LayoutComponent = layoutModule.Layout;
44
+ if (!LayoutComponent) {
45
+ throw new Error(`Layout component not found in ${layoutPath}. Make sure it exports 'Layout'.`);
46
+ }
47
+ // Create a wrapper App component that uses the discovered layout and passes pageData
48
+ const AppComponent = ({ Component, props }) => {
49
+ const LayoutWrapper = ({ children }) => {
50
+ return React.createElement(LayoutComponent, { pageData, children });
51
+ };
52
+ return React.createElement(LayoutWrapper, {
53
+ children: React.createElement(appModule.App, { Component, props, pageData })
54
+ });
55
+ };
56
+ const PageComponent = pageModule.default;
57
+ const getStaticProps = pageModule?.getStaticProps;
58
+ const getStaticPaths = pageModule?.getStaticPaths;
59
+ const injectJS = !excludedJSFiles.includes(page.pageName);
60
+ const rootId = crypto
61
+ .createHash("sha256")
62
+ .update(`app-${absolutePath}`)
63
+ .digest("hex")
64
+ .slice(0, 10);
65
+ const initialDatasId = crypto
66
+ .createHash("sha256")
67
+ .update(`initial-data-${absolutePath}`)
68
+ .digest("hex")
69
+ .slice(0, 10);
70
+ if (!PageComponent) {
71
+ throw new Error(`Failed to import PageComponent from ${page.pageName}.tsx`);
72
+ }
73
+ // Handle dynamic routes (pages with both getStaticProps and getStaticPaths)
74
+ if (getStaticPaths) {
75
+ const { paths } = await getStaticPaths();
76
+ if (paths && Array.isArray(paths)) {
77
+ for (const param of paths) {
78
+ if (param && param.params) {
79
+ // Extract parameter name from page name (e.g., "partials/dynamic/[id]" -> "id")
80
+ const paramMatch = page.pageName.match(/\[([^\]]+)\]/);
81
+ const paramKey = paramMatch ? paramMatch[1] : null;
82
+ const slug = paramKey ? param.params[paramKey] : null;
83
+ if (slug) {
84
+ const { props } = await getStaticProps(param);
85
+ const pageName = page.pageName.replace(/\[.*?\]/, slug);
86
+ const JSfileName = injectJS && fileName.replace(/\[(.*?)\]/g, "_$1_");
87
+ createPage({
88
+ data: props.data,
89
+ AppComponent,
90
+ PageComponent,
91
+ initialDatasId,
92
+ rootId,
93
+ pageName,
94
+ JSfileName: JSfileName,
95
+ pageData,
96
+ });
97
+ console.log(`✓ ${pageName}.html`);
98
+ }
99
+ }
100
+ else {
101
+ console.warn(`Skipping invalid path parameter for ${page.pageName}:`, param);
102
+ }
103
+ }
104
+ }
105
+ else {
106
+ console.warn(`No valid paths returned from getStaticPaths for ${page.pageName}`);
107
+ }
108
+ }
109
+ else {
110
+ // Handle static routes (pages without getStaticPaths)
111
+ if (getStaticProps) {
112
+ const { props } = await getStaticProps();
113
+ data = props.data;
114
+ }
115
+ createPage({
116
+ data,
117
+ AppComponent,
118
+ PageComponent,
119
+ initialDatasId,
120
+ rootId,
121
+ pageName: page.pageName,
122
+ JSfileName: injectJS && fileName,
123
+ pageData,
124
+ });
125
+ console.log(`✓ ${page.pageName}.html`);
126
+ }
127
+ }
128
+ catch (error) {
129
+ console.error(`Error processing ${page.pageName}:`, error);
130
+ }
131
+ };
132
+ const pages = Object.entries(files).map(([pageName, path]) => ({
133
+ pageName: pageName,
134
+ path: path,
135
+ }));
136
+ pages.forEach(processPage);
137
+ }
138
+ main();
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI tool for StaticJS
4
+ * Provides build, development, and start commands
5
+ */
6
+ export {};
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI tool for StaticJS
4
+ * Provides build, development, and start commands
5
+ */
6
+ import { Command } from 'commander';
7
+ import { execSync } from 'child_process';
8
+ import * as path from "node:path";
9
+ import * as fs from "node:fs";
10
+ import { fileURLToPath } from 'node:url';
11
+ const program = new Command();
12
+ // Get the directory where this CLI script is located
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ // Determine the lib directory path (where the staticjs package is installed)
16
+ const libDir = path.resolve(__dirname, '..');
17
+ // Function to find the nearest package.json to determine project root
18
+ function findProjectRoot() {
19
+ let currentDir = process.cwd();
20
+ while (currentDir !== path.parse(currentDir).root) {
21
+ const packageJsonPath = path.join(currentDir, 'package.json');
22
+ if (fs.existsSync(packageJsonPath)) {
23
+ return currentDir;
24
+ }
25
+ currentDir = path.dirname(currentDir);
26
+ }
27
+ // Fallback to current working directory
28
+ return process.cwd();
29
+ }
30
+ const projectRoot = findProjectRoot();
31
+ program
32
+ .name('static')
33
+ .description('StaticJS CLI tool')
34
+ .version('1.0.0');
35
+ program
36
+ .command('build')
37
+ .description('Build the static site')
38
+ .action(async () => {
39
+ try {
40
+ console.log('🔨 Building static site...');
41
+ console.log("\n1️⃣ Building static HTML files from TSX...");
42
+ const buildHtmlScript = path.join(libDir, 'scripts', 'build-html.js');
43
+ const staticHtmlFilesBuildCommand = `npx tsx "${buildHtmlScript}"`;
44
+ execSync(staticHtmlFilesBuildCommand, {
45
+ stdio: 'inherit',
46
+ cwd: projectRoot
47
+ });
48
+ console.log("\n2️⃣ Building assets with Vite...");
49
+ const viteConfigPath = path.join(libDir, 'server', 'config', 'vite.config.js');
50
+ const viteBuildCommand = `npx vite build --config "${viteConfigPath}"`;
51
+ execSync(viteBuildCommand, {
52
+ stdio: 'inherit',
53
+ cwd: projectRoot
54
+ });
55
+ console.log("\n3️⃣ Cleanup...");
56
+ const cacheDir = path.join(projectRoot, '_build', 'cache');
57
+ if (fs.existsSync(cacheDir)) {
58
+ const cleanupCommand = process.platform === 'win32'
59
+ ? `rmdir /s /q "${cacheDir}"`
60
+ : `rm -rf "${cacheDir}"`;
61
+ execSync(cleanupCommand, {
62
+ stdio: 'inherit',
63
+ cwd: projectRoot
64
+ });
65
+ }
66
+ console.log('\n✅ Build completed successfully!');
67
+ }
68
+ catch (error) {
69
+ console.error('\n❌ Build failed:', error);
70
+ process.exit(1);
71
+ }
72
+ });
73
+ program
74
+ .command('dev')
75
+ .description('Start development server')
76
+ .action(async () => {
77
+ try {
78
+ console.log('🚀 Starting development server...');
79
+ const serverEntrypoint = path.join(libDir, 'server', 'index.js');
80
+ const devCommand = `NODE_ENV=development tsx ${serverEntrypoint}`;
81
+ execSync(devCommand, {
82
+ stdio: 'inherit',
83
+ cwd: projectRoot
84
+ });
85
+ }
86
+ catch (error) {
87
+ console.error('❌ Development server failed:', error);
88
+ process.exit(1);
89
+ }
90
+ });
91
+ program
92
+ .command('start')
93
+ .description('Start production server to serve built static files')
94
+ .option('-p, --port <port>', 'Port to serve on', '3456')
95
+ .option('-h, --host <host>', 'Host to serve on', 'localhost')
96
+ .action(async (options) => {
97
+ try {
98
+ console.log('🌐 Starting production server...');
99
+ const buildDir = path.join(projectRoot, '_build');
100
+ // Check if build directory exists
101
+ if (!fs.existsSync(buildDir)) {
102
+ console.error('❌ Build directory not found. Please run "static build" first.');
103
+ process.exit(1);
104
+ }
105
+ console.log(`📁 Serving files from: ${buildDir}`);
106
+ console.log(`🔗 Server running at: http://${options.host}:${options.port}`);
107
+ // Use http-server to serve the built files
108
+ const startCommand = `npx http-server "${buildDir}" -p ${options.port} -a ${options.host} -c-1`;
109
+ execSync(startCommand, {
110
+ stdio: 'inherit',
111
+ cwd: projectRoot
112
+ });
113
+ }
114
+ catch (error) {
115
+ console.error('❌ Production server failed:', error);
116
+ process.exit(1);
117
+ }
118
+ });
119
+ program.parse();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate Test Multi-Apps CLI
4
+ * Creates multiple test applications for testing purposes
5
+ */
6
+ export {};
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate Test Multi-Apps CLI
4
+ * Creates multiple test applications for testing purposes
5
+ */
6
+ import { Command } from 'commander';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ const program = new Command();
10
+ program
11
+ .name('generate-test-multiapps')
12
+ .description('Generate multiple test applications')
13
+ .version('1.0.0')
14
+ .argument('<command>', 'Command to execute (generate:test)')
15
+ .action(async (command) => {
16
+ try {
17
+ if (command !== 'generate:test') {
18
+ console.error(`❌ Unknown command: ${command}`);
19
+ process.exit(1);
20
+ }
21
+ console.log('🧪 Generating test applications...');
22
+ const testApps = ['test-app-1', 'test-app-2', 'test-app-3'];
23
+ const baseDir = path.resolve(process.cwd(), 'test-apps');
24
+ // Create test apps directory
25
+ if (!fs.existsSync(baseDir)) {
26
+ fs.mkdirSync(baseDir, { recursive: true });
27
+ }
28
+ for (const appName of testApps) {
29
+ const appPath = path.join(baseDir, appName);
30
+ // Skip if already exists
31
+ if (fs.existsSync(appPath)) {
32
+ console.log(`⏭️ Skipping ${appName} (already exists)`);
33
+ continue;
34
+ }
35
+ console.log(`📁 Creating ${appName}...`);
36
+ fs.mkdirSync(appPath, { recursive: true });
37
+ // Create basic structure
38
+ const srcPath = path.join(appPath, 'src');
39
+ fs.mkdirSync(srcPath, { recursive: true });
40
+ // Create package.json
41
+ const packageJson = {
42
+ name: appName,
43
+ version: '1.0.0',
44
+ type: 'module',
45
+ scripts: {
46
+ dev: 'NODE_ENV=development tsx server.js',
47
+ build: 'bt-staticjs build',
48
+ start: 'npm run build && NODE_ENV=production tsx server.js'
49
+ },
50
+ dependencies: {
51
+ '@bouygues-telecom/staticjs': '*',
52
+ react: '^19.1.0',
53
+ 'react-dom': '^19.1.0'
54
+ }
55
+ };
56
+ fs.writeFileSync(path.join(appPath, 'package.json'), JSON.stringify(packageJson, null, 2));
57
+ // Create basic React component
58
+ const indexComponent = `import React from 'react';
59
+
60
+ export default function Index() {
61
+ return (
62
+ <div>
63
+ <h1>Welcome to ${appName}</h1>
64
+ <p>This is a test application generated for testing purposes.</p>
65
+ </div>
66
+ );
67
+ }
68
+ `;
69
+ fs.writeFileSync(path.join(srcPath, 'index.tsx'), indexComponent);
70
+ }
71
+ console.log('✅ Test applications generated successfully!');
72
+ console.log(`\nGenerated apps in: ${baseDir}`);
73
+ }
74
+ catch (error) {
75
+ console.error('❌ Failed to generate test apps:', error);
76
+ process.exit(1);
77
+ }
78
+ });
79
+ program.parse();
@@ -0,0 +1,20 @@
1
+ export interface ServerConfig {
2
+ PORT: number;
3
+ NODE_ENV: string;
4
+ PROJECT_ROOT: string;
5
+ BUILD_DIR: string;
6
+ REQUEST_TIMEOUT: number;
7
+ BODY_SIZE_LIMIT: string;
8
+ RATE_LIMIT_WINDOW: number;
9
+ RATE_LIMIT_MAX: number;
10
+ REVALIDATE_RATE_LIMIT_MAX: number;
11
+ CACHE_MAX_AGE: number;
12
+ HOT_RELOAD_ENABLED: boolean;
13
+ WEBSOCKET_ENABLED: boolean;
14
+ FILE_WATCHING_ENABLED: boolean;
15
+ WEBSOCKET_PATH: string;
16
+ FILE_WATCH_DEBOUNCE: number;
17
+ }
18
+ export declare const CONFIG: ServerConfig;
19
+ export declare const isDevelopment: boolean;
20
+ export declare const isProduction: boolean;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Server configuration constants
3
+ * Centralized configuration for the StaticJS React template server
4
+ */
5
+ import * as path from "node:path";
6
+ export const CONFIG = {
7
+ PORT: Number(process.env.PORT) || 3456,
8
+ NODE_ENV: process.env.NODE_ENV || 'development',
9
+ PROJECT_ROOT: path.resolve(process.cwd()),
10
+ BUILD_DIR: '_build',
11
+ REQUEST_TIMEOUT: 30000, // 30 seconds
12
+ BODY_SIZE_LIMIT: '10mb',
13
+ RATE_LIMIT_WINDOW: 15 * 60 * 1000, // 15 minutes
14
+ RATE_LIMIT_MAX: 100, // requests per window
15
+ REVALIDATE_RATE_LIMIT_MAX: 10, // stricter limit for revalidate endpoint
16
+ CACHE_MAX_AGE: process.env.NODE_ENV === 'production' ? 86400 : 0, // 1 day in prod, no cache in dev
17
+ // Hot reload configuration
18
+ HOT_RELOAD_ENABLED: process.env.NODE_ENV === 'development',
19
+ WEBSOCKET_ENABLED: process.env.NODE_ENV === 'development',
20
+ FILE_WATCHING_ENABLED: process.env.NODE_ENV === 'development',
21
+ WEBSOCKET_PATH: '/ws',
22
+ FILE_WATCH_DEBOUNCE: 300, // milliseconds
23
+ };
24
+ export const isDevelopment = CONFIG.NODE_ENV === 'development';
25
+ export const isProduction = CONFIG.NODE_ENV === 'production';
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfigFnObject;
2
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import path from "path";
2
+ import { defineConfig } from "vite";
3
+ import { addHydrationCodePlugin } from "./vite.plugin";
4
+ import { loadCacheEntries } from "../../helpers/cachePages.js";
5
+ import { CONFIG } from "./index";
6
+ // Load cache entries using the refactored helper function
7
+ const entries = loadCacheEntries(CONFIG.PROJECT_ROOT);
8
+ export default defineConfig(({ mode }) => {
9
+ // Load environment variables from .env files
10
+ // const env = loadEnv(mode, CONFIG.PROJECT_ROOT, '');
11
+ // console.log(`[Vite] Loaded environment variables for mode: ${mode}`, env);
12
+ return {
13
+ resolve: {
14
+ alias: {
15
+ "@": path.resolve(CONFIG.PROJECT_ROOT, "src")
16
+ },
17
+ },
18
+ build: {
19
+ outDir: path.resolve(CONFIG.PROJECT_ROOT, CONFIG.BUILD_DIR),
20
+ emptyOutDir: false,
21
+ rollupOptions: {
22
+ input: entries,
23
+ output: {
24
+ entryFileNames: "[name].js",
25
+ chunkFileNames: "assets/vendor-[hash].js",
26
+ },
27
+ },
28
+ },
29
+ plugins: [addHydrationCodePlugin(entries)],
30
+ };
31
+ });
@@ -0,0 +1,11 @@
1
+ export declare const addHydrationCodePlugin: (entries: {
2
+ [key: string]: string;
3
+ }) => {
4
+ name: string;
5
+ configResolved(config: any): void;
6
+ buildStart(): void;
7
+ transform(code: string, id: string): {
8
+ code: string;
9
+ map: null;
10
+ } | null;
11
+ };
@@ -0,0 +1,75 @@
1
+ import crypto from "node:crypto";
2
+ import path from "path";
3
+ import { findClosestLayout } from "../../helpers/layoutDiscovery.js";
4
+ const getDefaultExportFunctionName = (code) => {
5
+ const defaultExportRegex = /export\s+default\s+function\s+(\w+)/;
6
+ const match = code.match(defaultExportRegex);
7
+ if (match && match[1])
8
+ return match[1];
9
+ const defaultVarExportRegex = /export\s+default\s+(\w+)/;
10
+ const varMatch = code.match(defaultVarExportRegex);
11
+ if (varMatch && varMatch[1])
12
+ return varMatch[1];
13
+ return null;
14
+ };
15
+ export const addHydrationCodePlugin = (entries) => {
16
+ return {
17
+ name: "add-hydration-code",
18
+ configResolved(config) {
19
+ // Config resolved
20
+ },
21
+ buildStart() {
22
+ // Build started
23
+ },
24
+ transform(code, id) {
25
+ // Check if this is a page file
26
+ const isPageFile = Object.values(entries).includes(id);
27
+ if (!isPageFile) {
28
+ return null;
29
+ }
30
+ const componentName = getDefaultExportFunctionName(code);
31
+ if (!componentName) {
32
+ return null;
33
+ }
34
+ // Find the closest layout for this page
35
+ const rootDir = path.resolve(process.cwd(), "src");
36
+ const layoutPath = findClosestLayout(id, rootDir);
37
+ if (!layoutPath) {
38
+ console.warn(`No layout found for page ${id}, falling back to default App`);
39
+ return null;
40
+ }
41
+ // Get relative path for layout import
42
+ const layoutRelativePath = path.relative(path.dirname(id), layoutPath).replace(/\\/g, '/');
43
+ const layoutImportPath = layoutRelativePath.startsWith('.') ? layoutRelativePath : `./${layoutRelativePath}`;
44
+ // Component found, generating hydration code with discovered layout
45
+ const importReactDOM = `import ReactDOM from 'react-dom/client';`;
46
+ const importLayout = `import { Layout } from "${layoutImportPath.replace('.tsx', '')}";`;
47
+ const rootId = crypto
48
+ .createHash("sha256")
49
+ .update(`app-${id}`)
50
+ .digest("hex")
51
+ .slice(0, 10);
52
+ const initialDatasId = crypto
53
+ .createHash("sha256")
54
+ .update(`initial-data-${id}`)
55
+ .digest("hex")
56
+ .slice(0, 10);
57
+ const additionalCode = `
58
+ export const rootId = 'app-${rootId}';
59
+ export const initialDatasId = 'initial-data-${initialDatasId}';
60
+
61
+ if (typeof document !== 'undefined') {
62
+ document.addEventListener('DOMContentLoaded', () => {
63
+ const initialDataScript = document.getElementById(initialDatasId);
64
+ const initialData = initialDataScript ? JSON.parse(initialDataScript.textContent || '{}') : {title: ''};
65
+ ReactDOM.hydrateRoot(document.getElementById(rootId), React.createElement(${componentName}, {data: initialData}));
66
+ });
67
+ }`;
68
+ const transformedCode = importReactDOM + "\n" + importLayout + "\n" + code + "\n" + additionalCode;
69
+ return {
70
+ code: transformedCode,
71
+ map: null,
72
+ };
73
+ },
74
+ };
75
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Main server entry point for StaticJS React template
3
+ * Modular Express.js server with enhanced security, performance, and developer experience
4
+ */
5
+ import { Express } from "express";
6
+ /**
7
+ * Create and configure Express application
8
+ * @returns Promise<Express> - Configured Express application
9
+ */
10
+ export declare const createApp: () => Promise<Express>;
11
+ /**
12
+ * Initialize and start the server
13
+ * @returns Promise<Express> - Running Express application
14
+ */
15
+ export declare const startStaticJSServer: () => Promise<Express>;
16
+ export { isDevelopment } from "./config/index.js";
17
+ export type { ServerConfig } from "./config/index.js";
18
+ export { initializeViteServer } from "./utils/vite.js";
19
+ export { setupProcessHandlers, startServer } from "./utils/startup.js";
20
+ declare let app: Express | undefined;
21
+ export default app;
22
+ export declare const main: () => Promise<Express>;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Main server entry point for StaticJS React template
3
+ * Modular Express.js server with enhanced security, performance, and developer experience
4
+ */
5
+ import express from "express";
6
+ // Import configuration
7
+ import { CONFIG, isDevelopment } from "./config/index.js";
8
+ // Import middleware modules
9
+ import { applySecurity } from "./middleware/security.js";
10
+ import { applyPerformance } from "./middleware/performance.js";
11
+ import { applyRateLimiting } from "./middleware/rateLimiting.js";
12
+ import { applyParsing } from "./middleware/parsing.js";
13
+ import { applyLogging } from "./middleware/logging.js";
14
+ import { applyRuntime } from "./middleware/runtime.js";
15
+ import { applyHotReload } from "./middleware/hotReload.js";
16
+ import { applyStatic } from "./middleware/static.js";
17
+ import { applyErrorHandling } from "./middleware/errorHandling.js";
18
+ // Import route handlers
19
+ import { registerApiRoutes } from "./routes/api.js";
20
+ // Import utilities
21
+ import { initializeViteServer } from "./utils/vite.js";
22
+ import { setupProcessHandlers, startServer } from "./utils/startup.js";
23
+ import { initializeWebSocketServer } from "./utils/websocket.js";
24
+ import { initializeFileWatcher } from "./utils/fileWatcher.js";
25
+ // Singleton pattern to prevent duplicate server initialization
26
+ let serverInstance = null;
27
+ let isServerStarting = false;
28
+ /**
29
+ * Create and configure Express application
30
+ * @returns Promise<Express> - Configured Express application
31
+ */
32
+ export const createApp = async () => {
33
+ // Creating Express application
34
+ const app = express();
35
+ // Apply middleware in the correct order
36
+ // Applying middleware
37
+ applySecurity(app);
38
+ applyPerformance(app);
39
+ applyRateLimiting(app);
40
+ applyParsing(app);
41
+ applyLogging(app);
42
+ // Hot reload middleware (development mode only) - MUST be before runtime
43
+ applyHotReload(app);
44
+ // Initialize Vite server and register JavaScript routes BEFORE runtime middleware
45
+ if (isDevelopment) {
46
+ await initializeViteServer(app);
47
+ }
48
+ // Runtime rendering middleware (development mode only)
49
+ // JavaScript routes are now registered before this middleware
50
+ applyRuntime(app);
51
+ // Static file serving - comes after runtime to avoid interfering with JS serving
52
+ applyStatic(app);
53
+ // API routes
54
+ registerApiRoutes(app);
55
+ // Error handling (must be last)
56
+ applyErrorHandling(app);
57
+ return app;
58
+ };
59
+ /**
60
+ * Initialize and start the server
61
+ * @returns Promise<Express> - Running Express application
62
+ */
63
+ export const startStaticJSServer = async () => {
64
+ // Starting StaticJS server
65
+ // Prevent duplicate initialization
66
+ if (serverInstance) {
67
+ // Server already running
68
+ return serverInstance;
69
+ }
70
+ if (isServerStarting) {
71
+ // Server is already starting, waiting
72
+ // Wait for the other initialization to complete
73
+ while (isServerStarting && !serverInstance) {
74
+ await new Promise(resolve => setTimeout(resolve, 100));
75
+ }
76
+ return serverInstance;
77
+ }
78
+ isServerStarting = true;
79
+ try {
80
+ // Create Express app
81
+ const app = await createApp();
82
+ serverInstance = app;
83
+ // Start server (Vite is already initialized in createApp)
84
+ const server = await startServer(app);
85
+ // Initialize WebSocket server for hot reloading FIRST (development only)
86
+ if (CONFIG.WEBSOCKET_ENABLED && CONFIG.FILE_WATCHING_ENABLED) {
87
+ const wsServer = initializeWebSocketServer(server);
88
+ console.log('[Server] WebSocket server initialized');
89
+ // Wait a moment to ensure WebSocket server is fully ready
90
+ await new Promise(resolve => setTimeout(resolve, 100));
91
+ // Initialize file watcher AFTER WebSocket server (development only)
92
+ const fileWatcher = initializeFileWatcher();
93
+ console.log('[Server] File watcher initialized');
94
+ }
95
+ // Setup graceful shutdown handlers
96
+ setupProcessHandlers(server);
97
+ isServerStarting = false;
98
+ return app;
99
+ }
100
+ catch (error) {
101
+ console.error('❌ Failed to start server:', error);
102
+ isServerStarting = false;
103
+ serverInstance = null;
104
+ process.exit(1);
105
+ }
106
+ };
107
+ // Export additional utilities for external use
108
+ export { isDevelopment } from "./config/index.js";
109
+ export { initializeViteServer } from "./utils/vite.js";
110
+ export { setupProcessHandlers, startServer } from "./utils/startup.js";
111
+ // Only start the server when this module is run directly (not when imported)
112
+ let app;
113
+ // Check if this module is being run directly
114
+ if (import.meta.url === `file://${process.argv[1]}`) {
115
+ console.log('[Server] Module executed directly, starting server...');
116
+ app = await startStaticJSServer();
117
+ }
118
+ else {
119
+ console.log('[Server] Module imported, not starting server automatically');
120
+ }
121
+ // Default export is the app creation function
122
+ export default app;
123
+ // Named export for the main function (for backwards compatibility)
124
+ export const main = startStaticJSServer;