Skip to content

Commit 470ce9e

Browse files
committed
feat(remix): Set formData as span data.
1 parent 1eeea62 commit 470ce9e

28 files changed

+1320
-5
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* This is intended to be a basic starting point for linting in your app.
3+
* It relies on recommended configs out of the box for simplicity, but you can
4+
* and should modify this configuration to best suit your team's needs.
5+
*/
6+
7+
/** @type {import('eslint').Linter.Config} */
8+
module.exports = {
9+
root: true,
10+
parserOptions: {
11+
ecmaVersion: "latest",
12+
sourceType: "module",
13+
ecmaFeatures: {
14+
jsx: true,
15+
},
16+
},
17+
env: {
18+
browser: true,
19+
commonjs: true,
20+
es6: true,
21+
},
22+
23+
// Base config
24+
extends: ["eslint:recommended"],
25+
26+
overrides: [
27+
// React
28+
{
29+
files: ["**/*.{js,jsx,ts,tsx}"],
30+
plugins: ["react", "jsx-a11y"],
31+
extends: [
32+
"plugin:react/recommended",
33+
"plugin:react/jsx-runtime",
34+
"plugin:react-hooks/recommended",
35+
"plugin:jsx-a11y/recommended",
36+
],
37+
settings: {
38+
react: {
39+
version: "detect",
40+
},
41+
formComponents: ["Form"],
42+
linkComponents: [
43+
{ name: "Link", linkAttribute: "to" },
44+
{ name: "NavLink", linkAttribute: "to" },
45+
],
46+
"import/resolver": {
47+
typescript: {},
48+
},
49+
},
50+
},
51+
52+
// Typescript
53+
{
54+
files: ["**/*.{ts,tsx}"],
55+
plugins: ["@typescript-eslint", "import"],
56+
parser: "@typescript-eslint/parser",
57+
settings: {
58+
"import/internal-regex": "^~/",
59+
"import/resolver": {
60+
node: {
61+
extensions: [".ts", ".tsx"],
62+
},
63+
typescript: {
64+
alwaysTryTypes: true,
65+
},
66+
},
67+
},
68+
extends: [
69+
"plugin:@typescript-eslint/recommended",
70+
"plugin:import/recommended",
71+
"plugin:import/typescript",
72+
],
73+
},
74+
75+
// Node
76+
{
77+
files: [".eslintrc.cjs", "server.js"],
78+
env: {
79+
node: true,
80+
},
81+
},
82+
],
83+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
/public/build
6+
.env
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Welcome to Remix!
2+
3+
- [Remix Docs](https://remix.run/docs)
4+
5+
## Development
6+
7+
Start the Remix development asset server and the Express server by running:
8+
9+
```sh
10+
npm run dev
11+
```
12+
13+
This starts your app in development mode, which will purge the server require cache when Remix rebuilds assets so you don't need a process manager restarting the express server.
14+
15+
## Deployment
16+
17+
First, build your app for production:
18+
19+
```sh
20+
npm run build
21+
```
22+
23+
Then run the app in production mode:
24+
25+
```sh
26+
npm start
27+
```
28+
29+
Now you'll need to pick a host to deploy it to.
30+
31+
### DIY
32+
33+
If you're familiar with deploying express applications you should be right at home just make sure to deploy the output of `remix build`
34+
35+
- `build/`
36+
- `public/build/`
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* By default, Remix will handle hydrating your app on the client for you.
3+
* 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` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.client
5+
*/
6+
7+
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
8+
import * as Sentry from '@sentry/remix';
9+
import { StrictMode, startTransition, useEffect } from 'react';
10+
import { hydrateRoot } from 'react-dom/client';
11+
12+
Sentry.init({
13+
environment: 'qa', // dynamic sampling bias to keep transactions
14+
dsn: window.ENV.SENTRY_DSN,
15+
tunnel: `http://localhost:3031/`, // proxy server
16+
integrations: [
17+
Sentry.browserTracingIntegration({
18+
useEffect,
19+
useLocation,
20+
useMatches,
21+
}),
22+
new Sentry.Replay(),
23+
],
24+
// Performance Monitoring
25+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
26+
// Session Replay
27+
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.
28+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
29+
});
30+
31+
Sentry.addEventProcessor(event => {
32+
if (
33+
event.type === 'transaction' &&
34+
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
35+
) {
36+
const eventId = event.event_id;
37+
if (eventId) {
38+
window.recordedTransactions = window.recordedTransactions || [];
39+
window.recordedTransactions.push(eventId);
40+
}
41+
}
42+
43+
return event;
44+
});
45+
46+
startTransition(() => {
47+
hydrateRoot(
48+
document,
49+
<StrictMode>
50+
<RemixBrowser />
51+
</StrictMode>,
52+
);
53+
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* By default, Remix will handle generating the HTTP Response for you.
3+
* 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` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.server
5+
*/
6+
7+
import { PassThrough } from 'node:stream';
8+
9+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
10+
import { createReadableStreamFromReadable } from '@remix-run/node';
11+
import { installGlobals } from '@remix-run/node';
12+
import { RemixServer } from '@remix-run/react';
13+
import * as Sentry from '@sentry/remix';
14+
import { isbot } from 'isbot';
15+
import { renderToPipeableStream } from 'react-dom/server';
16+
17+
installGlobals();
18+
19+
const ABORT_DELAY = 5_000;
20+
21+
Sentry.init({
22+
environment: 'qa', // dynamic sampling bias to keep transactions
23+
dsn: process.env.E2E_TEST_DSN,
24+
// Performance Monitoring
25+
tunnel: 'http://localhost:3031/', // proxy server
26+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
27+
sendDefaultPii: true,
28+
});
29+
30+
export const handleError = Sentry.wrapRemixHandleError;
31+
32+
export default function handleRequest(
33+
request: Request,
34+
responseStatusCode: number,
35+
responseHeaders: Headers,
36+
remixContext: EntryContext,
37+
loadContext: AppLoadContext,
38+
) {
39+
return isbot(request.headers.get('user-agent'))
40+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
41+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
42+
}
43+
44+
function handleBotRequest(
45+
request: Request,
46+
responseStatusCode: number,
47+
responseHeaders: Headers,
48+
remixContext: EntryContext,
49+
) {
50+
return new Promise((resolve, reject) => {
51+
let shellRendered = false;
52+
const { pipe, abort } = renderToPipeableStream(
53+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
54+
{
55+
onAllReady() {
56+
shellRendered = true;
57+
const body = new PassThrough();
58+
const stream = createReadableStreamFromReadable(body);
59+
60+
responseHeaders.set('Content-Type', 'text/html');
61+
62+
resolve(
63+
new Response(stream, {
64+
headers: responseHeaders,
65+
status: responseStatusCode,
66+
}),
67+
);
68+
69+
pipe(body);
70+
},
71+
onShellError(error: unknown) {
72+
reject(error);
73+
},
74+
onError(error: unknown) {
75+
responseStatusCode = 500;
76+
// Log streaming rendering errors from inside the shell. Don't log
77+
// errors encountered during initial shell rendering since they'll
78+
// reject and get logged in handleDocumentRequest.
79+
if (shellRendered) {
80+
console.error(error);
81+
}
82+
},
83+
},
84+
);
85+
86+
setTimeout(abort, ABORT_DELAY);
87+
});
88+
}
89+
90+
function handleBrowserRequest(
91+
request: Request,
92+
responseStatusCode: number,
93+
responseHeaders: Headers,
94+
remixContext: EntryContext,
95+
) {
96+
return new Promise((resolve, reject) => {
97+
let shellRendered = false;
98+
const { pipe, abort } = renderToPipeableStream(
99+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
100+
{
101+
onShellReady() {
102+
shellRendered = true;
103+
const body = new PassThrough();
104+
const stream = createReadableStreamFromReadable(body);
105+
106+
responseHeaders.set('Content-Type', 'text/html');
107+
108+
resolve(
109+
new Response(stream, {
110+
headers: responseHeaders,
111+
status: responseStatusCode,
112+
}),
113+
);
114+
115+
pipe(body);
116+
},
117+
onShellError(error: unknown) {
118+
reject(error);
119+
},
120+
onError(error: unknown) {
121+
responseStatusCode = 500;
122+
// Log streaming rendering errors from inside the shell. Don't log
123+
// errors encountered during initial shell rendering since they'll
124+
// reject and get logged in handleDocumentRequest.
125+
if (shellRendered) {
126+
console.error(error);
127+
}
128+
},
129+
},
130+
);
131+
132+
setTimeout(abort, ABORT_DELAY);
133+
});
134+
}

0 commit comments

Comments
 (0)