From f531ea263b5951f5455f7c6f39002fd692d54be7 Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Mon, 14 Nov 2022 15:22:23 -0500 Subject: [PATCH] chore: make entry.{client,server}.tsx and root.tsx optional Signed-off-by: Logan McAnsh --- .eslintignore | 2 + .../remix-dev/__tests__/readConfig-test.ts | 4 + packages/remix-dev/compiler/compileBrowser.ts | 2 +- .../plugins/serverEntryModulePlugin.ts | 2 +- packages/remix-dev/config.ts | 49 ++++++-- .../config/defaults/entry.client.tsx | 23 ++++ .../config/defaults/entry.server.tsx | 112 ++++++++++++++++++ packages/remix-dev/config/defaults/root.tsx | 33 ++++++ packages/remix-dev/rollup.config.js | 4 +- 9 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 packages/remix-dev/config/defaults/entry.client.tsx create mode 100644 packages/remix-dev/config/defaults/entry.server.tsx create mode 100644 packages/remix-dev/config/defaults/root.tsx diff --git a/.eslintignore b/.eslintignore index 559d94f603b..9f26ea79d4c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,3 +11,5 @@ integration/helpers/deno-template packages/remix-deno templates/deno + +packages/remix-dev/config/defaults diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts index a6cdea1f44e..586303330e3 100644 --- a/packages/remix-dev/__tests__/readConfig-test.ts +++ b/packages/remix-dev/__tests__/readConfig-test.ts @@ -21,6 +21,8 @@ describe("readConfig", () => { serverBuildPath: expect.any(String), assetsBuildDirectory: expect.any(String), relativeAssetsBuildDirectory: expect.any(String), + entryClientFilePath: expect.any(String), + entryServerFilePath: expect.any(String), tsconfigPath: expect.any(String), future: { v2_meta: expect.any(Boolean), @@ -34,7 +36,9 @@ describe("readConfig", () => { "devServerBroadcastDelay": 0, "devServerPort": Any, "entryClientFile": "entry.client.tsx", + "entryClientFilePath": Any, "entryServerFile": "entry.server.tsx", + "entryServerFilePath": Any, "future": Object { "v2_meta": Any, }, diff --git a/packages/remix-dev/compiler/compileBrowser.ts b/packages/remix-dev/compiler/compileBrowser.ts index 338dd8a7dd8..0b556da807a 100644 --- a/packages/remix-dev/compiler/compileBrowser.ts +++ b/packages/remix-dev/compiler/compileBrowser.ts @@ -61,7 +61,7 @@ const createEsbuildConfig = ( options: CompileOptions ): esbuild.BuildOptions | esbuild.BuildIncremental => { let entryPoints: esbuild.BuildOptions["entryPoints"] = { - "entry.client": path.resolve(config.appDirectory, config.entryClientFile), + "entry.client": config.entryClientFile, }; for (let id of Object.keys(config.routes)) { // All route entry points are virtual modules that will be loaded by the diff --git a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts index 56955b4ce4f..2e08c98bc9b 100644 --- a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts +++ b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts @@ -30,7 +30,7 @@ export function serverEntryModulePlugin(config: RemixConfig): Plugin { resolveDir: config.appDirectory, loader: "js", contents: ` -import * as entryServer from ${JSON.stringify(`./${config.entryServerFile}`)}; +import * as entryServer from ${JSON.stringify(config.entryServerFile)}; ${Object.keys(config.routes) .map((key, index) => { let route = config.routes[key]; diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index 5c8a67ec29f..8989b0a8127 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -189,11 +189,21 @@ export interface RemixConfig { */ entryClientFile: string; + /** + * The absolute path to the entry.client file. + */ + entryClientFilePath: string; + /** * The path to the entry.server file, relative to `config.appDirectory`. */ entryServerFile: string; + /** + * The absolute path to the entry.server file. + */ + entryServerFilePath: string; + /** * An object of all available routes, keyed by route id. */ @@ -353,15 +363,28 @@ export async function readConfig( appConfig.cacheDirectory || ".cache" ); - let entryClientFile = findEntry(appDirectory, "entry.client"); - if (!entryClientFile) { - throw new Error(`Missing "entry.client" file in ${appDirectory}`); - } + let defaultsDirectory = path.resolve(__dirname, "config", "defaults"); + let defaultEntryClient = path.resolve(defaultsDirectory, "entry.client.tsx"); + let defaultEntryServer = path.resolve(defaultsDirectory, "entry.server.tsx"); - let entryServerFile = findEntry(appDirectory, "entry.server"); - if (!entryServerFile) { - throw new Error(`Missing "entry.server" file in ${appDirectory}`); - } + let userEntryClientFile = findEntry(appDirectory, "entry.client"); + let userEntryServerFile = findEntry(appDirectory, "entry.server"); + + let entryClientFile = userEntryClientFile + ? userEntryClientFile + : "entry.client.tsx"; + + let entryServerFile = userEntryServerFile + ? userEntryServerFile + : "entry.server.tsx"; + + let entryClientFilePath = userEntryClientFile + ? path.resolve(appDirectory, userEntryClientFile) + : defaultEntryClient; + + let entryServerFilePath = userEntryServerFile + ? path.resolve(appDirectory, userEntryServerFile) + : defaultEntryServer; let serverBuildPath = "build/index.js"; switch (serverBuildTarget) { @@ -406,7 +429,7 @@ export async function readConfig( Number(process.env.REMIX_DEV_SERVER_WS_PORT) || (await getPort({ port: Number(appConfig.devServerPort) || 8002 })); // set env variable so un-bundled servers can use it - process.env.REMIX_DEV_SERVER_WS_PORT = `${devServerPort}`; + process.env.REMIX_DEV_SERVER_WS_PORT = String(devServerPort); let devServerBroadcastDelay = appConfig.devServerBroadcastDelay || 0; let defaultPublicPath = "/build/"; @@ -419,12 +442,10 @@ export async function readConfig( let publicPath = addTrailingSlash(appConfig.publicPath || defaultPublicPath); let rootRouteFile = findEntry(appDirectory, "root"); - if (!rootRouteFile) { - throw new Error(`Missing "root" route file in ${appDirectory}`); - } + let defaultRootRouteFile = path.resolve(defaultsDirectory, "root.tsx"); let routes: RouteManifest = { - root: { path: "", id: "root", file: rootRouteFile }, + root: { path: "", id: "root", file: rootRouteFile || defaultRootRouteFile }, }; if (fse.existsSync(path.resolve(appDirectory, "routes"))) { let conventionalRoutes = defineConventionalRoutes( @@ -488,7 +509,9 @@ export async function readConfig( appDirectory, cacheDirectory, entryClientFile, + entryClientFilePath, entryServerFile, + entryServerFilePath, devServerPort, devServerBroadcastDelay, assetsBuildDirectory: absoluteAssetsBuildDirectory, diff --git a/packages/remix-dev/config/defaults/entry.client.tsx b/packages/remix-dev/config/defaults/entry.client.tsx new file mode 100644 index 00000000000..50fc10e785b --- /dev/null +++ b/packages/remix-dev/config/defaults/entry.client.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { RemixBrowser } from "@remix-run/react"; +import { startTransition, StrictMode } from "react"; +import { hydrateRoot } from "react-dom/client"; + +function hydrate() { + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); +} + +if (window.requestIdleCallback) { + window.requestIdleCallback(hydrate); +} else { + // Safari doesn't support requestIdleCallback + // https://caniuse.com/requestidlecallback + window.setTimeout(hydrate, 1); +} diff --git a/packages/remix-dev/config/defaults/entry.server.tsx b/packages/remix-dev/config/defaults/entry.server.tsx new file mode 100644 index 00000000000..bebf4073f94 --- /dev/null +++ b/packages/remix-dev/config/defaults/entry.server.tsx @@ -0,0 +1,112 @@ +import * as React from "react"; +import { PassThrough } from "stream"; +import type { EntryContext } from "@remix-run/node"; +import { Response } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import isbot from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; + +const ABORT_DELAY = 5000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + 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 didError = false; + + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + const body = new PassThrough(); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(body, { + headers: responseHeaders, + status: didError ? 500 : responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + didError = true; + + console.error(error); + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + return new Promise((resolve, reject) => { + let didError = false; + + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + const body = new PassThrough(); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(body, { + headers: responseHeaders, + status: didError ? 500 : responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(err: unknown) { + reject(err); + }, + onError(error: unknown) { + didError = true; + + console.error(error); + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/packages/remix-dev/config/defaults/root.tsx b/packages/remix-dev/config/defaults/root.tsx new file mode 100644 index 00000000000..5adbfb92977 --- /dev/null +++ b/packages/remix-dev/config/defaults/root.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import type { MetaFunction } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; + +export const meta: MetaFunction = () => ({ + charset: "utf-8", + title: "New Remix App", + viewport: "width=device-width,initial-scale=1", +}); + +export default function App() { + return ( + + + + + + + + + + + + + ); +} diff --git a/packages/remix-dev/rollup.config.js b/packages/remix-dev/rollup.config.js index 6bb4b9ab048..7fddca93fbd 100644 --- a/packages/remix-dev/rollup.config.js +++ b/packages/remix-dev/rollup.config.js @@ -51,8 +51,8 @@ module.exports = function rollup() { { src: `${sourceDir}/package.json`, dest: [outputDir, outputDist] }, { src: `${sourceDir}/README.md`, dest: outputDir }, { - src: `${sourceDir}/compiler/shims`, - dest: [`${outputDir}/compiler`, `${outputDist}/compiler`], + src: `${sourceDir}/config/defaults`, + dest: [`${outputDir}/config`, `${outputDist}/config`], }, ], }),