feat: switch to server in dev & prod

This commit is contained in:
Aarnav Tale 2024-12-23 10:27:05 -05:00
parent 6cf343d623
commit 1c2c374ada
No known key found for this signature in database
6 changed files with 80 additions and 29 deletions

View File

@ -5,7 +5,8 @@
"type": "module",
"scripts": {
"build": "remix vite:build && vite build",
"dev": "remix vite:dev",
"dev": "node server/dev.mjs",
"start": "node build/headplane/server.js",
"typecheck": "tsc"
},
"dependencies": {
@ -43,6 +44,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@babel/preset-typescript": "^7.26.0",
"@remix-run/dev": "^2.15.0",
"@remix-run/route-config": "^2.15.0",
"autoprefixer": "^10.4.20",

View File

@ -110,6 +110,9 @@ importers:
specifier: ^3.23.8
version: 3.23.8
devDependencies:
'@babel/preset-typescript':
specifier: ^7.26.0
version: 7.26.0(@babel/core@7.26.0)
'@remix-run/dev':
specifier: ^2.15.0
version: 2.15.0(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))

33
server/dev.mjs Normal file
View File

@ -0,0 +1,33 @@
// This is a polyglot entrypoint for Headplane when running in development
// It does some silly magic to load the vite config, set some globals that
// are required to function, and create the vite development server.
import { createServer } from 'vite'
import { env, exit } from 'node:process'
import { log } from './utils.mjs'
log('DEVX', 'INFO', 'This script is only intended for development')
env.NODE_ENV = 'development'
// The production entrypoint uses a global called "PREFIX" to determine
// what route the application is being served at and a global called "BUILD"
// to determine the Remix handler. We need to set these globals here so that
// the development server can function correctly and override the production
// values.
log('DEVX', 'INFO', 'Creating Vite Development Server')
const server = await createServer({
server: {
middlewareMode: true
}
})
// This entrypoint is defined in the documentation to load the server
const build = await server.ssrLoadModule('virtual:remix/server-build')
// We already handle this logic in the Vite configuration
global.PREFIX = server.config.base.slice(0, -1)
global.BUILD = build
global.MODE = 'development'
global.MIDDLEWARE = server.middlewares
await import('./prod.mjs')

56
server.mjs → server/prod.mjs Executable file → Normal file
View File

@ -9,30 +9,16 @@ import { createReadStream, existsSync, statSync } from 'node:fs'
import { createServer } from 'node:http'
import { join, resolve } from 'node:path'
import { env } from 'node:process'
import { log } from './utils.mjs'
function log(level, message) {
const date = new Date().toISOString()
console.log(`${date} (${level}) [SRVX] ${message}`)
}
log('INFO', `Running with Node.js ${process.versions.node}`)
log('SRVX', 'INFO', `Running with Node.js ${process.versions.node}`)
try {
await access('./node_modules/@remix-run', constants.F_OK | constants.R_OK)
log('INFO', 'Found node_modules dependencies')
log('SRVX', 'INFO', 'Found node_modules dependencies')
} catch (error) {
log('ERROR', 'No node_modules found. Please run `pnpm install` first')
log('ERROR', error)
process.exit(1)
}
try {
await access('./build/server', constants.F_OK | constants.R_OK)
log('INFO', 'Found build directory')
} catch (error) {
const date = new Date().toISOString()
log('ERROR', 'No build directory found. Please run `pnpm build` first')
log('ERROR', error)
log('SRVX', 'ERROR', 'No node_modules found. Please run `pnpm install`')
log('SRVX', 'ERROR', error)
process.exit(1)
}
@ -46,16 +32,36 @@ const { default: mime } = await import('mime')
const port = env.PORT || 3000
const host = env.HOST || '0.0.0.0'
const buildPath = env.BUILD_PATH || './build'
// Because this is a dynamic import without an easily discernable path
// we gain the "deoptimization" we want so that Vite doesn't bundle this
const build = await import(resolve(join(buildPath, 'server', 'index.js')))
const baseDir = resolve(join(buildPath, 'client'))
const handler = remixRequestHandler(build, 'production')
if (!global.BUILD) {
try {
await access(join(buildPath, 'server'), constants.F_OK | constants.R_OK)
log('SRVX', 'INFO', 'Found build directory')
} catch (error) {
const date = new Date().toISOString()
log('SRVX', 'ERROR', 'No build found. Please run `pnpm build`')
log('SRVX', 'ERROR', error)
process.exit(1)
}
// Because this is a dynamic import without an easily discernable path
// we gain the "deoptimization" we want so that Vite doesn't bundle this
const build = await import(resolve(join(buildPath, 'server', 'index.js')))
global.BUILD = build
global.MODE = 'production'
}
const handler = remixRequestHandler(global.BUILD, global.MODE)
const http = createServer(async (req, res) => {
const url = new URL(`http://${req.headers.host}${req.url}`)
if (global.MIDDLEWARE) {
await new Promise(resolve => {
global.MIDDLEWARE(req, res, resolve)
})
}
if (!url.pathname.startsWith(PREFIX)) {
res.writeHead(404)
res.end()
@ -155,5 +161,5 @@ const http = createServer(async (req, res) => {
})
http.listen(port, host, () => {
log('INFO', `Running on ${host}:${port}`)
log('SRVX', 'INFO', `Running on ${host}:${port}`)
})

4
server/utils.mjs Normal file
View File

@ -0,0 +1,4 @@
export function log(topic, level, message) {
const date = new Date().toISOString()
console.log(`${date} (${level}) [${topic}] ${message}`)
}

View File

@ -17,15 +17,18 @@ if (!version) {
export default defineConfig(({ isSsrBuild }) => {
// If we have the Headplane entry we build it as a single
// server.mjs file that is built for production server bundle
// server/prod.mjs file that is built for production server bundle
// We know the remix invoked command is vite:build
if (!process.argv.includes('vite:build') && !process.argv.includes('vite:dev')) {
if (
process.env.NODE_ENV !== 'development'
&& !process.argv.includes('vite:build')
) {
return {
build: {
minify: false,
target: 'esnext',
rollupOptions: {
input: './server.mjs',
input: './server/prod.mjs',
output: {
entryFileNames: 'server.js',
dir: 'build/headplane',