141 lines
4.7 KiB
JavaScript
141 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execSync, spawn } from 'child_process';
|
|
import { setTimeout } from 'timers/promises';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const isWin = process.platform === 'win32';
|
|
const npmCmd = isWin ? 'npm.cmd' : 'npm';
|
|
const npxCmd = isWin ? 'npx.cmd' : 'npx';
|
|
|
|
// Skip build if SKIP_BUILD is set OR if dist/index.html already exists (called from npm run build)
|
|
if (!process.env.SKIP_BUILD && !fs.existsSync(path.join('dist', 'index.html'))) {
|
|
console.log('Building the React app...');
|
|
try {
|
|
execSync(`${npmCmd} run build`, { stdio: 'inherit', shell: isWin });
|
|
} catch (error) {
|
|
console.error('Build failed:', error.message);
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
console.log('Skipping build (already built or SKIP_BUILD=1)');
|
|
}
|
|
|
|
// Copy server.js and server folder to dist for iisnode
|
|
console.log('Copying server files to dist...');
|
|
|
|
try {
|
|
// Create dist-specific server.js for iisnode (serves from __dirname, not dist subfolder)
|
|
const distServerJs = `import express from 'express';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import seoMiddleware from './server/seo-middleware.js';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// Configure MIME types for JSX files
|
|
app.use((req, res, next) => {
|
|
if (req.path.endsWith('.jsx')) {
|
|
res.setHeader('Content-Type', 'application/javascript');
|
|
}
|
|
next();
|
|
});
|
|
|
|
// Serve static assets FIRST (CSS, JS, images, fonts) - these MUST work
|
|
app.use('/assets', express.static(path.join(__dirname, 'assets')));
|
|
app.use('/src', express.static(path.join(__dirname, '../src')));
|
|
app.use('/og', express.static(path.join(__dirname, 'og')));
|
|
app.use(express.static(__dirname, {
|
|
index: false, // Don't serve index.html automatically
|
|
setHeaders: (res, filePath) => {
|
|
// Only block HTML files - let everything else through
|
|
if (filePath.endsWith('.html')) {
|
|
res.status(404).end();
|
|
return;
|
|
}
|
|
}
|
|
}));
|
|
|
|
// CRITICAL: Apply SEO middleware AFTER static files
|
|
// This ensures HTML requests go through SEO middleware
|
|
// But static assets are served first
|
|
app.use(seoMiddleware);
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(\`Server running on port \${PORT}\`);
|
|
console.log('Dynamic SEO enabled!');
|
|
});
|
|
|
|
export default app;
|
|
`;
|
|
|
|
fs.writeFileSync(path.join('dist', 'server.js'), distServerJs, 'utf8');
|
|
|
|
// Copy server folder to dist
|
|
if (!fs.existsSync(path.join('dist', 'server'))) {
|
|
fs.mkdirSync(path.join('dist', 'server'));
|
|
}
|
|
|
|
// Copy server.js from server folder (if it exists)
|
|
if (fs.existsSync(path.join('server', 'server.js'))) {
|
|
fs.copyFileSync(
|
|
path.join('server', 'server.js'),
|
|
path.join('dist', 'server', 'server.js')
|
|
);
|
|
}
|
|
|
|
// Create dist-specific seo-middleware.js with correct paths
|
|
const seoMiddlewareJs = fs.readFileSync(path.join('server', 'seo-middleware.js'), 'utf8');
|
|
// Replace the path from ../dist to .. for dist deployment (handle all variations)
|
|
const distSeoMiddlewareJs = seoMiddlewareJs
|
|
.replace(/path\.join\(__dirname, '\.\.\/dist'/g, "path.join(__dirname, '..'")
|
|
.replace(/path\.join\(__dirname, '\.\.', 'dist'/g, "path.join(__dirname, '..'")
|
|
.replace(/path\.join\(__dirname, '\.\.\/dist',/g, "path.join(__dirname, '..',")
|
|
.replace(/path\.join\(__dirname, '\.\.\/dist"/g, "path.join(__dirname, '..\"")
|
|
.replace(/path\.join\(__dirname, '\.\.\/dist',\s*req\.path/g, "path.join(__dirname, '..', req.path");
|
|
|
|
fs.writeFileSync(path.join('dist', 'server', 'seo-middleware.js'), distSeoMiddlewareJs, 'utf8');
|
|
|
|
console.log('Server files copied successfully!');
|
|
} catch (error) {
|
|
console.error('Failed to copy server files:', error.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (process.env.PRERENDER === '1') {
|
|
let server;
|
|
try {
|
|
console.log('Starting development server for prerender...');
|
|
server = spawn(npmCmd, ['run', 'dev'], { stdio: 'inherit', shell: isWin });
|
|
// Wait for server to start
|
|
await setTimeout(5000);
|
|
|
|
console.log('Prerendering pages with react-snap...');
|
|
execSync(`${npxCmd} react-snap`, { stdio: 'inherit', shell: isWin });
|
|
} catch (error) {
|
|
console.warn('Prerendering skipped due to error (continuing to SEO):', error?.message || error);
|
|
} finally {
|
|
if (server) {
|
|
console.log('Stopping development server...');
|
|
server.kill();
|
|
}
|
|
}
|
|
} else {
|
|
console.log('Skipping react-snap prerender (set PRERENDER=1 to enable)');
|
|
}
|
|
|
|
console.log('Applying SEO optimizations...');
|
|
try {
|
|
execSync('node scripts/generate-seo.js', { stdio: 'inherit', shell: isWin });
|
|
} catch (error) {
|
|
console.error('SEO application failed:', error.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('JSX to HTML conversion completed successfully!');
|