From 32c7cc0df568b287f0b1f3e7b0e485bd540892c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Moreau?= Date: Mon, 4 Nov 2024 19:03:20 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20The=20buildDirectory=20op?= =?UTF-8?q?tion=20was=20not=20affecting=20the=20client=20base=20directory?= =?UTF-8?q?=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remix-unstable-custom-build/.eslintrc.cjs | 80 ++++++++++++ .../remix-unstable-custom-build/.gitignore | 6 + .../remix-unstable-custom-build/README.md | 36 +++++ .../app/entry.client.tsx | 18 +++ .../app/entry.server.tsx | 123 ++++++++++++++++++ .../remix-unstable-custom-build/app/root.tsx | 28 ++++ .../app/routes/_index.tsx | 54 ++++++++ .../app/server/index.ts | 12 ++ .../app/server/middleware.ts | 8 ++ .../app/styles/tailwind.css | 3 + .../app/utils/.client/public.ts | 3 + .../app/utils/.common/common.ts | 3 + .../app/utils/.server/secret.ts | 3 + .../app/utils/env.server.ts | 3 + .../remix-unstable-custom-build/package.json | 44 +++++++ .../postcss.config.js | 6 + .../public/favicon.ico | Bin 0 -> 16958 bytes .../public/images/database.svg | 17 +++ .../tailwind.config.ts | 11 ++ .../remix-unstable-custom-build/tsconfig.json | 32 +++++ .../vite.config.ts | 58 +++++++++ package-lock.json | 41 +++++- package.json | 2 +- src/node.ts | 19 +-- 24 files changed, 599 insertions(+), 11 deletions(-) create mode 100644 examples/remix-unstable-custom-build/.eslintrc.cjs create mode 100644 examples/remix-unstable-custom-build/.gitignore create mode 100644 examples/remix-unstable-custom-build/README.md create mode 100644 examples/remix-unstable-custom-build/app/entry.client.tsx create mode 100644 examples/remix-unstable-custom-build/app/entry.server.tsx create mode 100644 examples/remix-unstable-custom-build/app/root.tsx create mode 100644 examples/remix-unstable-custom-build/app/routes/_index.tsx create mode 100644 examples/remix-unstable-custom-build/app/server/index.ts create mode 100644 examples/remix-unstable-custom-build/app/server/middleware.ts create mode 100644 examples/remix-unstable-custom-build/app/styles/tailwind.css create mode 100644 examples/remix-unstable-custom-build/app/utils/.client/public.ts create mode 100644 examples/remix-unstable-custom-build/app/utils/.common/common.ts create mode 100644 examples/remix-unstable-custom-build/app/utils/.server/secret.ts create mode 100644 examples/remix-unstable-custom-build/app/utils/env.server.ts create mode 100644 examples/remix-unstable-custom-build/package.json create mode 100644 examples/remix-unstable-custom-build/postcss.config.js create mode 100644 examples/remix-unstable-custom-build/public/favicon.ico create mode 100644 examples/remix-unstable-custom-build/public/images/database.svg create mode 100644 examples/remix-unstable-custom-build/tailwind.config.ts create mode 100644 examples/remix-unstable-custom-build/tsconfig.json create mode 100644 examples/remix-unstable-custom-build/vite.config.ts diff --git a/examples/remix-unstable-custom-build/.eslintrc.cjs b/examples/remix-unstable-custom-build/.eslintrc.cjs new file mode 100644 index 0000000..0a90d39 --- /dev/null +++ b/examples/remix-unstable-custom-build/.eslintrc.cjs @@ -0,0 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + ignorePatterns: ["!**/.server", "!**/.client"], + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + "import/resolver": { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: ["plugin:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/typescript"], + }, + + // Node + { + files: [".eslintrc.cjs"], + env: { + node: true, + }, + }, + ], +}; diff --git a/examples/remix-unstable-custom-build/.gitignore b/examples/remix-unstable-custom-build/.gitignore new file mode 100644 index 0000000..ba8ecf4 --- /dev/null +++ b/examples/remix-unstable-custom-build/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/dist +.env diff --git a/examples/remix-unstable-custom-build/README.md b/examples/remix-unstable-custom-build/README.md new file mode 100644 index 0000000..6417d50 --- /dev/null +++ b/examples/remix-unstable-custom-build/README.md @@ -0,0 +1,36 @@ +# Welcome to Remix + Vite! + +📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/guides/vite) for details on supported features. + +## Development + +Run the Vite dev server: + +```shellscript +npm run dev +``` + +## Deployment + +First, build your app for production: + +```sh +npm run build +``` + +Then run the app in production mode: + +```sh +npm start +``` + +Now you'll need to pick a host to deploy it to. + +### DIY + +If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. + +Make sure to deploy the output of `npm run build` + +- `build/server` +- `build/client` diff --git a/examples/remix-unstable-custom-build/app/entry.client.tsx b/examples/remix-unstable-custom-build/app/entry.client.tsx new file mode 100644 index 0000000..dc318e7 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/entry.client.tsx @@ -0,0 +1,18 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import { RemixBrowser } from "@remix-run/react"; +import { StrictMode, startTransition } from "react"; +import { hydrateRoot } from "react-dom/client"; + +startTransition(() => { + hydrateRoot( + document, + + + + ); +}); diff --git a/examples/remix-unstable-custom-build/app/entry.server.tsx b/examples/remix-unstable-custom-build/app/entry.server.tsx new file mode 100644 index 0000000..ab9ce28 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/entry.server.tsx @@ -0,0 +1,123 @@ +/** + * By default, Remix will handle generating the HTTP Response for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.server + */ + +import { PassThrough } from "node:stream"; +import type { AppLoadContext, EntryContext } from "@remix-run/node"; +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import { isbot } from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; + +export * from "./server"; + +const ABORT_DELAY = 5_000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loadContext: AppLoadContext +) { + return isbot(request.headers.get("user-agent") || "") + ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) + : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/examples/remix-unstable-custom-build/app/root.tsx b/examples/remix-unstable-custom-build/app/root.tsx new file mode 100644 index 0000000..bc3925b --- /dev/null +++ b/examples/remix-unstable-custom-build/app/root.tsx @@ -0,0 +1,28 @@ +import type { LinksFunction } from "@remix-run/node"; +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"; +import styles from "~/styles/tailwind.css?url"; + +export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + + {children} + + + + + ); +} + +export default function App() { + return ; +} diff --git a/examples/remix-unstable-custom-build/app/routes/_index.tsx b/examples/remix-unstable-custom-build/app/routes/_index.tsx new file mode 100644 index 0000000..10d1490 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/routes/_index.tsx @@ -0,0 +1,54 @@ +import { type ClientLoaderFunctionArgs, useLoaderData, useRevalidator } from "@remix-run/react"; +import { getPublic } from "~/utils/.client/public"; +import { getCommon } from "~/utils/.common/common"; +import { getSecret } from "~/utils/.server/secret"; +import { getEnv } from "~/utils/env.server"; +import dbLogo from "/images/database.svg"; + +export function loader() { + console.log(getSecret(), getCommon()); + return { + env: getEnv(), + }; +} + +export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) { + console.log(getPublic(), getCommon()); + return { + ...(await serverLoader()), + }; +} + +clientLoader.hydrate = true; + +export default function Index() { + const data = useLoaderData(); + console.log(dbLogo); + const { revalidate } = useRevalidator(); + return ( +
+ +
+ + + + + + + + + {Object.entries(data.env).map(([key, value]) => ( + + + + + ))} + +
KeyValue
{key}{value ?? "-"}
+
+
+ ); +} diff --git a/examples/remix-unstable-custom-build/app/server/index.ts b/examples/remix-unstable-custom-build/app/server/index.ts new file mode 100644 index 0000000..76c510e --- /dev/null +++ b/examples/remix-unstable-custom-build/app/server/index.ts @@ -0,0 +1,12 @@ +import { createHonoServer } from "react-router-hono-server/node"; +import { exampleMiddleware } from "./middleware"; + +export const server = await createHonoServer({ + buildDirectory: "dist", + configure(server) { + server.use("*", exampleMiddleware()); + }, + listeningListener(info) { + console.log(`Server is listening on http://localhost:${info.port}`); + }, +}); diff --git a/examples/remix-unstable-custom-build/app/server/middleware.ts b/examples/remix-unstable-custom-build/app/server/middleware.ts new file mode 100644 index 0000000..4a3aba5 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/server/middleware.ts @@ -0,0 +1,8 @@ +import { createMiddleware } from "hono/factory"; + +export function exampleMiddleware() { + return createMiddleware(async (c, next) => { + console.log("accept-language", c.req.header("accept-language")); + return next(); + }); +} diff --git a/examples/remix-unstable-custom-build/app/styles/tailwind.css b/examples/remix-unstable-custom-build/app/styles/tailwind.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/styles/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/remix-unstable-custom-build/app/utils/.client/public.ts b/examples/remix-unstable-custom-build/app/utils/.client/public.ts new file mode 100644 index 0000000..04c748c --- /dev/null +++ b/examples/remix-unstable-custom-build/app/utils/.client/public.ts @@ -0,0 +1,3 @@ +export function getPublic() { + return "public"; +} diff --git a/examples/remix-unstable-custom-build/app/utils/.common/common.ts b/examples/remix-unstable-custom-build/app/utils/.common/common.ts new file mode 100644 index 0000000..9665a73 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/utils/.common/common.ts @@ -0,0 +1,3 @@ +export function getCommon() { + return "common"; +} diff --git a/examples/remix-unstable-custom-build/app/utils/.server/secret.ts b/examples/remix-unstable-custom-build/app/utils/.server/secret.ts new file mode 100644 index 0000000..d0a98b4 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/utils/.server/secret.ts @@ -0,0 +1,3 @@ +export function getSecret() { + return "secret"; +} diff --git a/examples/remix-unstable-custom-build/app/utils/env.server.ts b/examples/remix-unstable-custom-build/app/utils/env.server.ts new file mode 100644 index 0000000..a038201 --- /dev/null +++ b/examples/remix-unstable-custom-build/app/utils/env.server.ts @@ -0,0 +1,3 @@ +export function getEnv() { + return { ...process.env }; +} diff --git a/examples/remix-unstable-custom-build/package.json b/examples/remix-unstable-custom-build/package.json new file mode 100644 index 0000000..fd5ed27 --- /dev/null +++ b/examples/remix-unstable-custom-build/package.json @@ -0,0 +1,44 @@ +{ + "name": "remix-unstable-custom-build", + "private": true, + "sideEffects": true, + "type": "module", + "scripts": { + "build": "NODE_ENV=production remix vite:build", + "dev": "vite --host", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", + "start": "NODE_ENV=production node ./dist/server/index.js", + "typecheck": "tsc" + }, + "dependencies": { + "@remix-run/node": "^2.11.1", + "@remix-run/react": "^2.11.1", + "isbot": "^4.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-hono-server": "*" + }, + "devDependencies": { + "@remix-run/dev": "^2.11.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "autoprefixer": "^10.4.20", + "esbuild": "^0.23.1", + "eslint": "^8.38.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.7", + "typescript": "^5.1.6", + "vite": "^5.1.0", + "vite-tsconfig-paths": "^4.2.1" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/remix-unstable-custom-build/postcss.config.js b/examples/remix-unstable-custom-build/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/examples/remix-unstable-custom-build/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/examples/remix-unstable-custom-build/public/favicon.ico b/examples/remix-unstable-custom-build/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61 GIT binary patch literal 16958 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 EKbET!0{{R3 literal 0 HcmV?d00001 diff --git a/examples/remix-unstable-custom-build/public/images/database.svg b/examples/remix-unstable-custom-build/public/images/database.svg new file mode 100644 index 0000000..e052da1 --- /dev/null +++ b/examples/remix-unstable-custom-build/public/images/database.svg @@ -0,0 +1,17 @@ + + + + + + + diff --git a/examples/remix-unstable-custom-build/tailwind.config.ts b/examples/remix-unstable-custom-build/tailwind.config.ts new file mode 100644 index 0000000..f4bdc80 --- /dev/null +++ b/examples/remix-unstable-custom-build/tailwind.config.ts @@ -0,0 +1,11 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./app/**/*.{js,jsx,ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; + +export default config; diff --git a/examples/remix-unstable-custom-build/tsconfig.json b/examples/remix-unstable-custom-build/tsconfig.json new file mode 100644 index 0000000..9d87dd3 --- /dev/null +++ b/examples/remix-unstable-custom-build/tsconfig.json @@ -0,0 +1,32 @@ +{ + "include": [ + "**/*.ts", + "**/*.tsx", + "**/.server/**/*.ts", + "**/.server/**/*.tsx", + "**/.client/**/*.ts", + "**/.client/**/*.tsx" + ], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["@remix-run/node", "vite/client"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "target": "ES2022", + "strict": true, + "allowJs": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + + // Vite takes care of building everything, not tsc. + "noEmit": true + } +} diff --git a/examples/remix-unstable-custom-build/vite.config.ts b/examples/remix-unstable-custom-build/vite.config.ts new file mode 100644 index 0000000..4695388 --- /dev/null +++ b/examples/remix-unstable-custom-build/vite.config.ts @@ -0,0 +1,58 @@ +import fs from "node:fs"; +import { vitePlugin as remix } from "@remix-run/dev"; +import { installGlobals } from "@remix-run/node"; +import esbuild from "esbuild"; +import { devServer } from "react-router-hono-server/dev"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +installGlobals({ nativeFetch: true }); + +export default defineConfig({ + build: { + target: "esnext", + }, + plugins: [ + devServer(), + remix({ + buildDirectory: "dist", + future: { + v3_singleFetch: true, + }, + // For Sentry instrumentation + // https://docs.sentry.io/platforms/javascript/guides/remix/manual-setup/#custom-express-server + // buildEnd: async ({ remixConfig }) => { + // const sentryInstrument = `instrument.server`; + // await esbuild + // .build({ + // alias: { + // "~": "./app", + // }, + // outdir: `${remixConfig.buildDirectory}/server`, + // entryPoints: [`${remixConfig.appDirectory}/server/${sentryInstrument}.ts`], + // platform: "node", + // format: "esm", + // // Don't include node_modules in the bundle + // packages: "external", + // bundle: true, + // logLevel: "info", + // }) + // .then(() => { + // const serverBuildPath = `${remixConfig.buildDirectory}/server/${remixConfig.serverBuildFile}`; + // fs.writeFileSync( + // serverBuildPath, + // Buffer.concat([ + // Buffer.from(`import "./${sentryInstrument}.js"\n`), + // Buffer.from(fs.readFileSync(serverBuildPath)), + // ]) + // ); + // }) + // .catch((error: unknown) => { + // console.error(error); + // process.exit(1); + // }); + // }, + }), + tsconfigPaths(), + ], +}); diff --git a/package-lock.json b/package-lock.json index ea19d6e..023469a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-router-hono-server", - "version": "0.3.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-router-hono-server", - "version": "0.3.0", + "version": "0.5.0", "license": "ISC", "workspaces": [ ".", @@ -102,6 +102,39 @@ "node": ">=18.0.0" } }, + "examples/remix-unstable-custom-build": { + "dependencies": { + "@remix-run/node": "^2.11.1", + "@remix-run/react": "^2.11.1", + "isbot": "^4.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-hono-server": "*" + }, + "devDependencies": { + "@remix-run/dev": "^2.11.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "autoprefixer": "^10.4.20", + "esbuild": "^0.23.1", + "eslint": "^8.38.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.7", + "typescript": "^5.1.6", + "vite": "^5.1.0", + "vite-tsconfig-paths": "^4.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -11462,6 +11495,10 @@ "resolved": "examples/remix-unstable", "link": true }, + "node_modules/remix-unstable-custom-build": { + "resolved": "examples/remix-unstable-custom-build", + "link": true + }, "node_modules/require-like": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", diff --git a/package.json b/package.json index bf9a9d6..863eb3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-router-hono-server", - "version": "0.4.0", + "version": "0.5.0", "description": "This package includes helper function to create an Hono app in your entry.server.tsx file. It allows you to customize your server.", "exports": { "./node": { diff --git a/src/node.ts b/src/node.ts index 9f728c2..295cd55 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,3 +1,4 @@ +import type { AddressInfo } from "node:net"; import path from "node:path"; import url from "node:url"; import { serve } from "@hono/node-server"; @@ -27,7 +28,7 @@ export type HonoServerOptions = { /** * The directory where the server build files are located (defined in vite.config) * - * Defaults to `build/server` + * Defaults to `build` * * See https://remix.run/docs/en/main/file-conventions/vite-config#builddirectory */ @@ -78,7 +79,7 @@ export type HonoServerOptions = { * * Defaults log the port */ - listeningListener?: (info: { port: number }) => void; + listeningListener?: (info: AddressInfo) => void; /** * Hono constructor options * @@ -90,7 +91,7 @@ export type HonoServerOptions = { const defaultOptions: HonoServerOptions = { defaultLogger: true, port: Number(process.env.PORT) || 3000, - buildDirectory: "build/server", + buildDirectory: "build", serverBuildFile: "index.js", assetsDir: "assets", listeningListener: (info) => { @@ -116,19 +117,23 @@ export async function createHonoServer(options: HonoSe const server = new Hono(mergedOptions.honoOptions); + const serverBuildPath = `./${mergedOptions.buildDirectory}/server`; + + const clientBuildPath = `./${mergedOptions.buildDirectory}/client`; + /** * Serve assets files from build/client/assets */ server.use( `/${mergedOptions.assetsDir}/*`, cache(60 * 60 * 24 * 365), // 1 year - serveStatic({ root: "./build/client" }) + serveStatic({ root: clientBuildPath }) ); /** * Serve public files */ - server.use("*", cache(60 * 60), serveStatic({ root: isProductionMode ? "./build/client" : "./public" })); // 1 hour + server.use("*", cache(60 * 60), serveStatic({ root: isProductionMode ? clientBuildPath : "./public" })); // 1 hour /** * Add logger middleware @@ -155,9 +160,7 @@ export async function createHonoServer(options: HonoSe /* @vite-ignore */ url .pathToFileURL( - path.resolve( - path.join(process.cwd(), `./${mergedOptions.buildDirectory}/${mergedOptions.serverBuildFile}`) - ) + path.resolve(path.join(process.cwd(), `${serverBuildPath}/${mergedOptions.serverBuildFile}`)) ) .toString() )