From 61593b9024341afd135759374df1089764c7d785 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 4 Sep 2023 14:37:04 +0100 Subject: [PATCH 01/15] feat(remix): Add flags for Remix v2 usage. --- .github/workflows/build.yml | 1 + .../create-remix-app-v2/.eslintrc.js | 4 + .../create-remix-app-v2/.gitignore | 6 + .../create-remix-app-v2/.npmrc | 2 + .../create-remix-app-v2/README.md | 58 +++++ .../create-remix-app-v2/app/entry.client.tsx | 49 +++++ .../create-remix-app-v2/app/entry.server.tsx | 125 +++++++++++ .../create-remix-app-v2/app/root.tsx | 65 ++++++ .../create-remix-app-v2/app/routes/_index.tsx | 21 ++ .../app/routes/client-error.tsx | 13 ++ .../app/routes/navigate.tsx | 20 ++ .../app/routes/user.$id.tsx | 3 + .../create-remix-app-v2/globals.d.ts | 7 + .../create-remix-app-v2/package.json | 35 +++ .../create-remix-app-v2/playwright.config.ts | 69 ++++++ .../create-remix-app-v2/remix.config.js | 9 + .../create-remix-app-v2/remix.env.d.ts | 2 + .../tests/behaviour-client.test.ts | 208 ++++++++++++++++++ .../create-remix-app-v2/tsconfig.json | 22 ++ .../create-remix-app/app/entry.client.tsx | 17 +- .../create-remix-app/app/entry.server.tsx | 14 +- .../create-remix-app/app/root.tsx | 42 +++- .../create-remix-app/app/routes/_index.tsx | 39 ++-- .../app/routes/client-error.tsx | 13 ++ .../create-remix-app/app/routes/navigate.tsx | 20 ++ .../create-remix-app/app/routes/user.$id.tsx | 3 + .../create-remix-app/globals.d.ts | 7 + .../create-remix-app/package.json | 12 +- .../create-remix-app/remix.config.js | 2 + .../tests/behaviour-client.test.ts | 205 ++++++++++++++++- .../app/route-handlers/[param]/edge/route.ts | 11 - .../app/route-handlers/[param]/error/route.ts | 3 - .../app/route-handlers/[param]/route.ts | 9 - .../tests/route-handlers.test.ts | 95 -------- packages/ember/index.d.ts | 9 + .../sentry-performance.d.ts | 12 + packages/ember/runloop.d.ts | 19 ++ packages/ember/types.d.ts | 35 +++ packages/remix/src/client/errors.tsx | 13 +- packages/remix/src/index.client.tsx | 3 +- packages/remix/src/index.server.ts | 5 +- packages/remix/src/utils/instrumentServer.ts | 9 +- packages/remix/src/utils/metadata.ts | 6 +- packages/remix/src/utils/remixOptions.ts | 4 +- 44 files changed, 1157 insertions(+), 169 deletions(-) create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/.eslintrc.js create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/.gitignore create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/.npmrc create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/README.md create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/root.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/client-error.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/navigate.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/user.$id.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/globals.d.ts create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/package.json create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/remix.config.js create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/remix.env.d.ts create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-client.test.ts create mode 100644 packages/e2e-tests/test-applications/create-remix-app-v2/tsconfig.json create mode 100644 packages/e2e-tests/test-applications/create-remix-app/app/routes/client-error.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app/app/routes/navigate.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app/app/routes/user.$id.tsx create mode 100644 packages/e2e-tests/test-applications/create-remix-app/globals.d.ts delete mode 100644 packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/edge/route.ts delete mode 100644 packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/error/route.ts delete mode 100644 packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/route.ts delete mode 100644 packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts create mode 100644 packages/ember/index.d.ts create mode 100644 packages/ember/instance-initializers/sentry-performance.d.ts create mode 100644 packages/ember/runloop.d.ts create mode 100644 packages/ember/types.d.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbc80ec59f33..56f61126a45a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -815,6 +815,7 @@ jobs: 'create-react-app', 'create-next-app', 'create-remix-app', + 'create-remix-app-v2', 'nextjs-app-dir', 'react-create-hash-router', 'react-router-6-use-routes', diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/.eslintrc.js b/packages/e2e-tests/test-applications/create-remix-app-v2/.eslintrc.js new file mode 100644 index 000000000000..2061cd22684d --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/.eslintrc.js @@ -0,0 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], +}; diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/.gitignore b/packages/e2e-tests/test-applications/create-remix-app-v2/.gitignore new file mode 100644 index 000000000000..3f7bf98da3e1 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/public/build +.env diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/.npmrc b/packages/e2e-tests/test-applications/create-remix-app-v2/.npmrc new file mode 100644 index 000000000000..c6b3ef9b3eaa --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://localhost:4873 +@sentry-internal:registry=http://localhost:4873 diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/README.md b/packages/e2e-tests/test-applications/create-remix-app-v2/README.md new file mode 100644 index 000000000000..3a7335c73c9e --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/README.md @@ -0,0 +1,58 @@ +# Welcome to Remix! + +- [Remix Docs](https://remix.run/docs) + +## Development + +From your terminal: + +```sh +npm run dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## 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 `remix build` + +- `build/` +- `public/build/` + +### Using a Template + +When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over relevant code/assets from your current app to the new project that's pre-configured for your target server. + +Most importantly, this means everything in the `app/` directory, but if you've further customized your current application outside of there it may also include: + +- Any assets you've added/updated in `public/` +- Any updated versions of root files such as `.eslintrc.js`, etc. + +```sh +cd .. +# create a new project, and pick a pre-configured host +npx create-remix@latest +cd my-new-remix-app +# remove the new project's app (not the old one!) +rm -rf app +# copy your app over +cp -R ../my-old-remix-app/app app +``` diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx b/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx new file mode 100644 index 000000000000..605d8e792d23 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx @@ -0,0 +1,49 @@ +/** + * 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, useLocation, useMatches } from '@remix-run/react'; +import { startTransition, StrictMode, useEffect } from 'react'; +import { hydrateRoot } from 'react-dom/client'; +import * as Sentry from '@sentry/remix'; + +Sentry.init({ + dsn: window.ENV.SENTRY_DSN, + integrations: [ + new Sentry.BrowserTracing({ + routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches), + }), + new Sentry.Replay(), + ], + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + // Session Replay + replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. +}); + +Sentry.addGlobalEventProcessor(event => { + if ( + event.type === 'transaction' && + (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') + ) { + const eventId = event.event_id; + if (eventId) { + window.recordedTransactions = window.recordedTransactions || []; + window.recordedTransactions.push(eventId); + } + } + + return event; +}); + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx b/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx new file mode 100644 index 000000000000..9b1dbdaac5f6 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx @@ -0,0 +1,125 @@ +/** + * 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 { Response } from '@remix-run/node'; +import { RemixServer } from '@remix-run/react'; +import isbot from 'isbot'; +import { renderToPipeableStream } from 'react-dom/server'; +import * as Sentry from '@sentry/remix'; + +const ABORT_DELAY = 5_000; + +Sentry.init({ + dsn: process.env.E2E_TEST_DSN, + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + isRemixV2: true, +}); + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + 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(); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(body, { + 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(); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(body, { + 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/packages/e2e-tests/test-applications/create-remix-app-v2/app/root.tsx b/packages/e2e-tests/test-applications/create-remix-app-v2/app/root.tsx new file mode 100644 index 000000000000..2e76a3c7d0a1 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-remix-app-v2/app/root.tsx @@ -0,0 +1,65 @@ +import { cssBundleHref } from '@remix-run/css-bundle'; +import { json, LinksFunction } from '@remix-run/node'; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, + useRouteError, +} from '@remix-run/react'; +import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; + +export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])]; + +export const loader = () => { + return json({ + ENV: { + SENTRY_DSN: process.env.E2E_TEST_DSN, + }, + }); +}; + +export const ErrorBoundary = () => { + const error = useRouteError() as { eventId?: string | Promise | undefined }; + const eventId = captureRemixErrorBoundaryError(error); + + return ( +
+ ErrorBoundary Error + {eventId} +
+ ); +}; + +function App() { + const { ENV } = useLoaderData(); + + return ( + + + + +