rasengan
Version:
The modern React Framework
181 lines (180 loc) • 6.32 kB
JavaScript
import { join } from 'node:path';
import express from 'express';
import { createServerModuleRunner, createServer as createViteServer, } from 'vite';
import chalk from 'chalk';
import inquirer from 'inquirer';
// Middlewares
import { loggerMiddleware } from '../../core/middlewares/index.js';
// Load utilities functions
import { generateRandomPort, isDataRequest, isDocumentRequest, logServerInfo, } from './utils.js';
import { getDirname, loadModuleSSR, } from '../../core/config/utils/load-modules.js';
import { ServerMode } from '../runtime/mode.js';
import { handleDataRequest, handleDocumentRequest, handleSpaModeRequest, } from './handlers.js';
import { createStaticHandler } from 'react-router';
import { generateRoutes } from '../../routing/utils/generate-routes.js';
/**
* Handle the request for the development environment
* @param req
* @param res
* @param viteDevServer
* @param options
*/
async function devRequestHandler(req, res, viteDevServer, options) {
try {
// Get the module runner through ssr environment
const runner = createServerModuleRunner(viteDevServer.environments.ssr);
if (options.config.ssr) {
// Load app-router
const AppRouter = await (await runner.import(join(`${options.rootPath}/src/app/app.router`))).default;
// Get static routes
const staticRoutes = generateRoutes(AppRouter);
// Create static handler
let handler = createStaticHandler(staticRoutes);
if (isDataRequest(req)) {
// Handle data request
return await handleDataRequest(req, handler);
}
if (isDocumentRequest(req)) {
return await handleDocumentRequest(req, res, runner, {
...options,
handler,
});
}
}
else {
// handling spa mode here
return await handleSpaModeRequest(res, runner, options);
}
return res.status(404).send('Not found');
}
catch (error) {
// Just log the error for now
console.error(error);
}
}
/**
* Handle server errors
* @param err
* @param options
* @returns
*/
async function errorHandler(err, options) {
const { port, base, enableSearchingPort, config } = options;
const multiplicationSymbol = '\u00D7';
// Handle PORT in use error
if (err.code === 'EADDRINUSE') {
const newPort = port + 1;
console.error(chalk.red(`${chalk.bold.red(multiplicationSymbol)} Port ${port} is already in use. \n\n`));
// Check if user wants to use a different port
if (!enableSearchingPort) {
// Ask user if they want to use a different port
const { useDifferentPort } = await inquirer.prompt([
{
type: 'confirm',
name: 'useDifferentPort',
message: `Do you want to use a different port?`,
},
]);
if (!useDifferentPort) {
console.log(chalk.blue('Closing server... \n\n'));
process.exit(0);
}
console.log(chalk.blue(`Trying port ${newPort}... \n\n`));
return await createDevNodeServer({
port: newPort,
base,
enableSearchingPort: true,
config,
});
}
else {
console.log(chalk.blue(`Trying port ${newPort}... \n\n`));
return await createDevNodeServer({
port: newPort,
base,
enableSearchingPort,
config,
});
}
}
}
/**
* This function is responsible for creating a server for the development environment.
*/
async function createDevNodeServer({ port, base = '/', enableSearchingPort = false, config, }) {
// Get app path
const rootPath = process.cwd();
// Get directory full path
const __dirname = await getDirname(import.meta.url);
// Create http server
const app = express();
// Initialize a vite dev server as middleware
const viteDevServer = await createViteServer({
server: {
middlewareMode: true,
hmr: {
// TODO: Find a way to use a random port
port: generateRandomPort(),
},
},
base,
configFile: `${rootPath}/node_modules/rasengan/vite.config.ts`, // Path: [...]/node_modules/rasengan/vite.config.ts
});
// Disable x-powered-by
app.disable('x-powered-by');
// Apply middlewares
app.use(loggerMiddleware);
app.use(viteDevServer.middlewares);
// Create the request handler
app.use('*', async (req, res, next) => {
try {
// const url = req.url || req.originalUrl;
return devRequestHandler(req, res, viteDevServer, {
rootPath,
__dirname,
config,
});
}
catch (error) {
if (typeof error === 'object' && error instanceof Error) {
viteDevServer.ssrFixStacktrace(error);
}
next(error);
// TODO: Find a way to handle the error here
}
});
// Start http server
const server = app.listen(port, () => {
setTimeout(() => {
logServerInfo(port, ServerMode.Development, config.server?.development?.open);
}, 100);
});
// Handle errors
server.on('error', async (err) => {
await errorHandler(err, {
port,
base,
enableSearchingPort,
config,
});
});
}
// Launch server
(async function launchDevNodeServer() {
const rootPath = process.cwd();
// Format config path
const configPath = join(`${rootPath}/rasengan.config.js`);
// Get config
const configHandler = await (await loadModuleSSR(configPath)).default;
// console.log({ configHandler });
const config = await configHandler();
const port = (process.env.PORT && Number(process.env.PORT)) ||
config.server?.development?.port ||
5320;
const base = process.env.BASE || '/';
await createDevNodeServer({
port,
base,
config,
});
})();