import {createRequestHandler} from '@remix-run/express'; import express from 'express'; import morgan from 'morgan'; import sylvite from 'sylvite'; import environment from './environment.server.js'; const cacheDirectory = `${import.meta.dirname}/node_modules/.cache`; const {hooks} = await sylvite({ entry: 'node', ...await import('./sylvite.config.js'), }); const { httpsCert, httpsKey, isInsecure, isProduction, port, useMkcert, } = environment(); const app = express(); hooks.call('@/sylvite/cha0s-remix:initializeExpress', app); const httpServerOptions = hooks.call('@/sylvite/cha0s-remix:httpServerOptions', {}); let server; if (isInsecure) { // http:// const {createServer} = await import('node:http'); server = createServer(httpServerOptions, app); } else { const {readFile} = await import('node:fs/promises'); // generate certificates if (useMkcert) { const {execSync} = await import('node:child_process'); const {mkdirSync, statSync} = await import('node:fs'); mkdirSync(cacheDirectory, {recursive: true}); try { statSync(`${cacheDirectory}/localhost-key.pem`); } catch (error) { // eslint-disable-line no-unused-vars execSync(`mkcert -cert-file ${cacheDirectory}/localhost.pem -key-file ${cacheDirectory}/localhost-key.pem localhost`) } } // https:// const [cert, key] = await Promise.all([readFile(httpsCert), readFile(httpsKey)]); const serverOptions = { cert, key, ...httpServerOptions, }; const {createServer} = await import('node:https'); server = createServer(serverOptions, app); } // immediately start listening and queueing up connections let resolve, promise = new Promise((res) => { resolve = res; }); app.use(async (req, res, next) => { await promise; next(); }); server.listen(port, () => { console.log(`Express server listening at http${isInsecure ? '' : 's'}://localhost:${port}`); }); const requestHandlerOptions = { getLoadContext: (req) => { return hooks.call('@/sylvite/cha0s-remix:getRemixLoadContext', {}, req); }, }; if (isProduction) { // remix handler requestHandlerOptions.build = () => import('./build/server/index.js'); // websocket handler const {entry: {module: {handleUpgrade}}} = await requestHandlerOptions.build(); server.on('upgrade', handleUpgrade); // serve assets app.use('/assets', express.static('build/client/assets', { // vite fingerprints its assets so we can cache forever. immutable: true, maxAge: '1y', })); app.use(express.static('build/client', { // everything else (like favicon.ico) is cached for an hour. maxAge: '1h', })); } else { const viteDevServer = await import('vite').then((vite) => ( vite.createServer({ server: { middlewareMode: true, }, }) )); // remix handler requestHandlerOptions.build = () => viteDevServer.ssrLoadModule('virtual:remix/server-build'); const {createViteRuntime} = await import('vite'); // websocket handler const runtime = await createViteRuntime(viteDevServer); const {handleUpgrade} = await runtime.executeEntrypoint('/app/websocket.js'); server.on('upgrade', handleUpgrade); // serve assets app.use(viteDevServer.middlewares); app.get('/storybook', async (req, res) => { const url = new URL(req.url, `http${isInsecure ? '' : 's'}://${req.headers.host}`); url.pathname = '/'; url.port = 11337; res.redirect(url.href); }); } hooks.call('@/sylvite/cha0s-remix:additionalAssets', app); app.use(morgan('tiny')); hooks.call('@/sylvite/cha0s-remix:beforeExpressCatchAll', app); // handle SSR requests app.all('*', createRequestHandler(requestHandlerOptions)); // finalize; let requests resolve resolve();