feat: use all native node deps for the server

This commit is contained in:
Aarnav Tale 2024-11-04 22:05:46 -05:00
parent 12754bd0aa
commit 5c949e2da5
No known key found for this signature in database
4 changed files with 170 additions and 104 deletions

View File

@ -4,7 +4,7 @@
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "remix vite:build", "build": "remix vite:build && vite build",
"dev": "remix vite:dev", "dev": "remix vite:dev",
"typecheck": "tsc" "typecheck": "tsc"
}, },
@ -13,7 +13,6 @@
"@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@hono/node-server": "^1.13.5",
"@kubernetes/client-node": "^0.22.2", "@kubernetes/client-node": "^0.22.2",
"@primer/octicons-react": "^19.12.0", "@primer/octicons-react": "^19.12.0",
"@react-aria/toast": "3.0.0-beta.12", "@react-aria/toast": "3.0.0-beta.12",
@ -25,15 +24,14 @@
"@uiw/react-codemirror": "^4.23.6", "@uiw/react-codemirror": "^4.23.6",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"hono": "^4.6.9",
"isbot": "^5.1.17", "isbot": "^5.1.17",
"mime": "^4.0.4",
"oauth4webapi": "^2.17.0", "oauth4webapi": "^2.17.0",
"react": "19.0.0-beta-26f2496093-20240514", "react": "19.0.0-beta-26f2496093-20240514",
"react-aria-components": "^1.2.1", "react-aria-components": "^1.2.1",
"react-codemirror-merge": "^4.23.6", "react-codemirror-merge": "^4.23.6",
"react-dom": "19.0.0-beta-26f2496093-20240514", "react-dom": "19.0.0-beta-26f2496093-20240514",
"react-error-boundary": "^4.1.2", "react-error-boundary": "^4.1.2",
"remix-hono": "^0.0.16",
"remix-utils": "^7.7.0", "remix-utils": "^7.7.0",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-react-aria-components": "^1.1.6", "tailwindcss-react-aria-components": "^1.1.6",

View File

@ -28,9 +28,6 @@ importers:
'@dnd-kit/utilities': '@dnd-kit/utilities':
specifier: ^3.2.2 specifier: ^3.2.2
version: 3.2.2(react@19.0.0-beta-26f2496093-20240514) version: 3.2.2(react@19.0.0-beta-26f2496093-20240514)
'@hono/node-server':
specifier: ^1.13.5
version: 1.13.5(hono@4.6.9)
'@kubernetes/client-node': '@kubernetes/client-node':
specifier: ^0.22.2 specifier: ^0.22.2
version: 0.22.2 version: 0.22.2
@ -64,12 +61,12 @@ importers:
dotenv: dotenv:
specifier: ^16.4.5 specifier: ^16.4.5
version: 16.4.5 version: 16.4.5
hono:
specifier: ^4.6.9
version: 4.6.9
isbot: isbot:
specifier: ^5.1.17 specifier: ^5.1.17
version: 5.1.17 version: 5.1.17
mime:
specifier: ^4.0.4
version: 4.0.4
oauth4webapi: oauth4webapi:
specifier: ^2.17.0 specifier: ^2.17.0
version: 2.17.0 version: 2.17.0
@ -88,9 +85,6 @@ importers:
react-error-boundary: react-error-boundary:
specifier: ^4.1.2 specifier: ^4.1.2
version: 4.1.2(react@19.0.0-beta-26f2496093-20240514) version: 4.1.2(react@19.0.0-beta-26f2496093-20240514)
remix-hono:
specifier: ^0.0.16
version: 0.0.16(typescript@5.6.3)(zod@3.23.8)
remix-utils: remix-utils:
specifier: ^7.7.0 specifier: ^7.7.0
version: 7.7.0(@remix-run/node@2.13.1(typescript@5.6.3))(@remix-run/react@2.13.1(react-dom@19.0.0-beta-26f2496093-20240514(react@19.0.0-beta-26f2496093-20240514))(react@19.0.0-beta-26f2496093-20240514)(typescript@5.6.3))(@remix-run/router@1.20.0)(react@19.0.0-beta-26f2496093-20240514)(zod@3.23.8) version: 7.7.0(@remix-run/node@2.13.1(typescript@5.6.3))(@remix-run/react@2.13.1(react-dom@19.0.0-beta-26f2496093-20240514(react@19.0.0-beta-26f2496093-20240514))(react@19.0.0-beta-26f2496093-20240514)(typescript@5.6.3))(@remix-run/router@1.20.0)(react@19.0.0-beta-26f2496093-20240514)(zod@3.23.8)
@ -666,12 +660,6 @@ packages:
'@formatjs/intl-localematcher@0.5.4': '@formatjs/intl-localematcher@0.5.4':
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
'@hono/node-server@1.13.5':
resolution: {integrity: sha512-lSo+CFlLqAFB4fX7ePqI9nauEn64wOfJHAfc9duYFTvAG3o416pC0nTGeNjuLHchLedH+XyWda5v79CVx1PIjg==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@internationalized/date@3.5.6': '@internationalized/date@3.5.6':
resolution: {integrity: sha512-jLxQjefH9VI5P9UQuqB6qNKnvFt1Ky1TPIzHGsIlCi7sZZoMR8SdYbBGRvM0y+Jtb+ez4ieBzmiAUcpmPYpyOw==} resolution: {integrity: sha512-jLxQjefH9VI5P9UQuqB6qNKnvFt1Ky1TPIzHGsIlCi7sZZoMR8SdYbBGRvM0y+Jtb+ez4ieBzmiAUcpmPYpyOw==}
@ -2279,10 +2267,6 @@ packages:
hast-util-whitespace@2.0.1: hast-util-whitespace@2.0.1:
resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
hono@4.6.9:
resolution: {integrity: sha512-p/pN5yZLuZaHzyAOT2nw2/Ud6HhJHYmDNGH6Ck1OWBhPMVeM1r74jbCRwNi0gyFRjjbsGgoHbOyj7mT1PDNbTw==}
engines: {node: '>=16.9.0'}
hosted-git-info@6.1.1: hosted-git-info@6.1.1:
resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==} resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@ -2737,6 +2721,11 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
mime@4.0.4:
resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==}
engines: {node: '>=16'}
hasBin: true
mimic-fn@2.1.0: mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -3084,10 +3073,6 @@ packages:
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
hasBin: true hasBin: true
pretty-cache-header@1.0.0:
resolution: {integrity: sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==}
engines: {node: '>=12.13'}
pretty-format@24.9.0: pretty-format@24.9.0:
resolution: {integrity: sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==} resolution: {integrity: sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -3252,23 +3237,6 @@ packages:
remark-rehype@10.1.0: remark-rehype@10.1.0:
resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
remix-hono@0.0.16:
resolution: {integrity: sha512-IPooI2E0eSFRV9wAgTzpsklSoOyij6Nsk6ANIugGhD9vYlovbYb2BT+siz++sLzWseZSPEtIzx/LBel203jBfQ==}
peerDependencies:
'@remix-run/cloudflare': ^2.0.0
i18next: ^23.0.0
remix-i18next: ^6.0.0
zod: ^3.0.0
peerDependenciesMeta:
'@remix-run/cloudflare':
optional: true
i18next:
optional: true
remix-i18next:
optional: true
zod:
optional: true
remix-utils@7.7.0: remix-utils@7.7.0:
resolution: {integrity: sha512-J8NhP044nrNIam/xOT1L9a4RQ9FSaA2wyrUwmN8ZT+c/+CdAAf70yfaLnvMyKcV5U+8BcURQ/aVbth77sT6jGA==} resolution: {integrity: sha512-J8NhP044nrNIam/xOT1L9a4RQ9FSaA2wyrUwmN8ZT+c/+CdAAf70yfaLnvMyKcV5U+8BcURQ/aVbth77sT6jGA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@ -3562,10 +3530,6 @@ packages:
through2@2.0.5: through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
timestring@6.0.0:
resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==}
engines: {node: '>=8'}
to-fast-properties@2.0.0: to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -4390,10 +4354,6 @@ snapshots:
dependencies: dependencies:
tslib: 2.6.2 tslib: 2.6.2
'@hono/node-server@1.13.5(hono@4.6.9)':
dependencies:
hono: 4.6.9
'@internationalized/date@3.5.6': '@internationalized/date@3.5.6':
dependencies: dependencies:
'@swc/helpers': 0.5.11 '@swc/helpers': 0.5.11
@ -6645,8 +6605,6 @@ snapshots:
hast-util-whitespace@2.0.1: {} hast-util-whitespace@2.0.1: {}
hono@4.6.9: {}
hosted-git-info@6.1.1: hosted-git-info@6.1.1:
dependencies: dependencies:
lru-cache: 7.18.3 lru-cache: 7.18.3
@ -7230,6 +7188,8 @@ snapshots:
mime@1.6.0: {} mime@1.6.0: {}
mime@4.0.4: {}
mimic-fn@2.1.0: {} mimic-fn@2.1.0: {}
minimatch@9.0.5: minimatch@9.0.5:
@ -7559,10 +7519,6 @@ snapshots:
prettier@2.8.8: {} prettier@2.8.8: {}
pretty-cache-header@1.0.0:
dependencies:
timestring: 6.0.0
pretty-format@24.9.0: pretty-format@24.9.0:
dependencies: dependencies:
'@jest/types': 24.9.0 '@jest/types': 24.9.0
@ -7833,16 +7789,6 @@ snapshots:
mdast-util-to-hast: 12.3.0 mdast-util-to-hast: 12.3.0
unified: 10.1.2 unified: 10.1.2
remix-hono@0.0.16(typescript@5.6.3)(zod@3.23.8):
dependencies:
'@remix-run/server-runtime': 2.13.1(typescript@5.6.3)
hono: 4.6.9
pretty-cache-header: 1.0.0
optionalDependencies:
zod: 3.23.8
transitivePeerDependencies:
- typescript
remix-utils@7.7.0(@remix-run/node@2.13.1(typescript@5.6.3))(@remix-run/react@2.13.1(react-dom@19.0.0-beta-26f2496093-20240514(react@19.0.0-beta-26f2496093-20240514))(react@19.0.0-beta-26f2496093-20240514)(typescript@5.6.3))(@remix-run/router@1.20.0)(react@19.0.0-beta-26f2496093-20240514)(zod@3.23.8): remix-utils@7.7.0(@remix-run/node@2.13.1(typescript@5.6.3))(@remix-run/react@2.13.1(react-dom@19.0.0-beta-26f2496093-20240514(react@19.0.0-beta-26f2496093-20240514))(react@19.0.0-beta-26f2496093-20240514)(typescript@5.6.3))(@remix-run/router@1.20.0)(react@19.0.0-beta-26f2496093-20240514)(zod@3.23.8):
dependencies: dependencies:
type-fest: 4.26.1 type-fest: 4.26.1
@ -8199,8 +8145,6 @@ snapshots:
readable-stream: 2.3.8 readable-stream: 2.3.8
xtend: 4.0.2 xtend: 4.0.2
timestring@6.0.0: {}
to-fast-properties@2.0.0: {} to-fast-properties@2.0.0: {}
to-regex-range@5.0.1: to-regex-range@5.0.1:

View File

@ -1,6 +1,13 @@
#!/usr/bin/env node // This is a polyglot entrypoint for Headplane when running in production
// It doesn't use any dependencies aside from @remix-run/node and mime
// During build we bundle the used dependencies into the file so that
// we can only need this file and a Node.js installation to run the server.
// PREFIX is defined globally, see vite.config.ts
import { access, constants } from 'node:fs/promises' import { access, constants } from 'node:fs/promises'
import { createReadStream, existsSync, statSync } from 'node:fs'
import { createServer } from 'node:http'
import { join, resolve } from 'node:path'
function log(level, message) { function log(level, message) {
const date = new Date().toISOString() const date = new Date().toISOString()
@ -28,25 +35,105 @@ try {
process.exit(1) process.exit(1)
} }
const { installGlobals } = await import('@remix-run/node') const {
const { remix } = await import('remix-hono/handler') createRequestHandler: remixRequestHandler,
const { serve } = await import('@hono/node-server') createReadableStreamFromReadable,
const { Hono } = await import('hono') writeReadableStreamToWritable
} = await import('@remix-run/node')
const { default: mime } = await import('mime')
installGlobals()
const app = new Hono()
const port = process.env.PORT || 3000 const port = process.env.PORT || 3000
const host = process.env.HOST || '0.0.0.0' const host = process.env.HOST || '0.0.0.0'
const buildPath = process.env.BUILD_PATH || './build'
app.use('*', remix({ // Because this is a dynamic import without an easily discernable path
build: await import('./build/server/index.js'), // we gain the "deoptimization" we want so that Vite doesn't bundle this
mode: 'production' const build = await import(resolve(join(buildPath, 'server', 'index.js')))
})) const baseDir = resolve(join(buildPath, 'client'))
serve({ const handler = remixRequestHandler(build, 'production')
fetch: app.fetch, const http = createServer(async (req, res) => {
hostname: host, const url = new URL(`http://${req.headers.host}${req.url}`)
port
// Before we pass any requests to our Remix handler we need to check
// if we can handle a raw file request. This is important for the
// Remix loader to work correctly.
//
// To optimize this, we send them as readable streams in the node
// response and we also set headers for aggressive caching.
if (url.pathname.startsWith(`${PREFIX}assets/`)) {
const filePath = join(baseDir, url.pathname.replace(PREFIX, ''))
const exists = existsSync(filePath)
const stats = statSync(filePath)
if (exists && stats.isFile()) {
// Build assets are cache-bust friendly so we can cache them heavily
if (req.url.startsWith('/build')) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
}
// Send the file as a readable stream
const fileStream = createReadStream(filePath)
const type = mime.getType(filePath)
res.setHeader('Content-Length', stats.size)
res.setHeader('Content-Type', type)
fileStream.pipe(res)
return
}
}
// Handling the request
const controller = new AbortController()
res.on('close', () => controller.abort())
const headers = new Headers()
for (const [key, value] of Object.entries(req.headers)) {
if (!value) continue
if (Array.isArray(value)) {
for (const v of value) {
headers.append(key, v)
}
continue
}
headers.append(key, value)
}
const remixReq = new Request(url.href, {
headers,
method: req.method,
signal: controller.signal,
// If we have a body we set a duplex and we load the body
...(req.method !== 'GET' && req.method !== 'HEAD' ? {
body: createReadableStreamFromReadable(req),
duplex: 'half'
} : {}
)
})
// Pass our request to the Remix handler and get a response
const response = await handler(remixReq, {}) // No context
// Handle our response and reply
res.statusCode = response.status
res.statusMessage = response.statusText
for (const [key, value] of response.headers.entries()) {
res.appendHeader(key, value)
}
if (response.body) {
await writeReadableStreamToWritable(response.body, res)
return
}
res.end()
}) })
log('INFO', `Running on ${host}:${port}`) http.listen(port, host, () => {
log('INFO', `Running on ${host}:${port}`)
})

View File

@ -6,22 +6,59 @@ import tsconfigPaths from 'vite-tsconfig-paths'
installGlobals() installGlobals()
export default defineConfig(({ isSsrBuild }) => ({ const prefix = process.env.__INTERNAL_PREFIX || '/admin/'
base: '/admin/', if (!prefix.endsWith('/')) {
build: isSsrBuild ? { target: 'ES2022' } : {}, throw new Error('Prefix must end with a slash')
plugins: [ }
remix({
basename: '/admin/', export default defineConfig(({ isSsrBuild }) => {
}), // If we have the Headplane entry we build it as a single
tsconfigPaths(), // server.mjs file that is built for production server bundle
babel({ // We know the remix invoked command is vite:build
filter: /\.[jt]sx?$/, if (!process.argv.includes('vite:build')) {
babelConfig: { return {
presets: ['@babel/preset-typescript'], build: {
plugins: [ minify: false,
['babel-plugin-react-compiler', {}], target: 'esnext',
], rollupOptions: {
input: './server.mjs',
output: {
entryFileNames: 'server.js',
dir: 'build/headplane',
banner: '#!/usr/bin/env node\n',
},
external: (id) => id.startsWith('node:'),
}
}, },
}), define: {
], PREFIX: JSON.stringify(prefix),
})) },
resolve: {
alias: {
stream: 'node:stream',
crypto: 'node:crypto',
}
}
}
}
return ({
base: prefix,
build: isSsrBuild ? { target: 'ES2022' } : {},
plugins: [
remix({
basename: prefix,
}),
tsconfigPaths(),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ['@babel/preset-typescript'],
plugins: [
['babel-plugin-react-compiler', {}],
],
},
}),
],
})
})