@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,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,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();
|
|
File without changes
|
|
@@ -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,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,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;
|