Skip to content

Commit a2e576f

Browse files
feat: Add support for Remix (Vite) (#140)
Add in new Remix Codecov plugin.
1 parent e7be5b4 commit a2e576f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+4775
-189
lines changed

.changeset/big-socks-impress.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@codecov/sveltekit-plugin": patch
3+
"@codecov/remix-plugin": patch
4+
"@codecov/bundler-plugin-core": patch
5+
"@codecov/nuxt-plugin": patch
6+
"@codecov/rollup-plugin": patch
7+
"@codecov/vite-plugin": patch
8+
"@codecov/webpack-plugin": patch
9+
---
10+
11+
Fix SvelteKit plugin keywords

.changeset/shy-turtles-shout.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@codecov/sveltekit-plugin": patch
3+
"@codecov/remix-plugin": patch
4+
"@codecov/bundler-plugin-core": patch
5+
"@codecov/nuxt-plugin": patch
6+
"@codecov/rollup-plugin": patch
7+
"@codecov/vite-plugin": patch
8+
"@codecov/webpack-plugin": patch
9+
---
10+
11+
Add in new Remix plugin

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const config = {
2828
"./integration-tests/tsconfig.json",
2929
"./packages/bundler-plugin-core/tsconfig.json",
3030
"./packages/nuxt-plugin/tsconfig.json",
31+
"./packages/remix-plugin/tsconfig.json",
3132
"./packages/rollup-plugin/tsconfig.json",
3233
"./packages/sveltekit-plugin/tsconfig.json",
3334
"./packages/vite-plugin/tsconfig.json",

.github/workflows/ci.yml

+10-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ jobs:
279279
strategy:
280280
fail-fast: false
281281
matrix:
282-
example: ["next-js", "nuxt", "rollup", "sveltekit", "vite", "webpack"]
282+
example:
283+
["next-js", "nuxt", "remix", "rollup", "sveltekit", "vite", "webpack"]
283284
steps:
284285
- name: Checkout
285286
uses: actions/checkout@v4
@@ -329,6 +330,8 @@ jobs:
329330
NEXT_API_URL: ${{ secrets.CODECOV_API_URL }}
330331
NUXT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
331332
NUXT_API_URL: ${{ secrets.CODECOV_API_URL }}
333+
REMIX_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
334+
REMIX_API_URL: ${{ secrets.CODECOV_API_URL }}
332335
ROLLUP_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
333336
ROLLUP_API_URL: ${{ secrets.CODECOV_API_URL }}
334337
SVELTEKIT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
@@ -346,7 +349,8 @@ jobs:
346349
strategy:
347350
fail-fast: false
348351
matrix:
349-
example: ["next-js", "nuxt", "rollup", "sveltekit", "vite", "webpack"]
352+
example:
353+
["next-js", "nuxt", "remix", "rollup", "sveltekit", "vite", "webpack"]
350354
steps:
351355
- name: Checkout
352356
uses: actions/checkout@v4
@@ -396,6 +400,8 @@ jobs:
396400
NEXT_API_URL: ${{ secrets.CODECOV_STAGING_API_URL }}
397401
NUXT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
398402
NUXT_API_URL: ${{ secrets.CODECOV_API_URL }}
403+
REMIX_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
404+
REMIX_API_URL: ${{ secrets.CODECOV_API_URL }}
399405
ROLLUP_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN_STAGING }}
400406
ROLLUP_API_URL: ${{ secrets.CODECOV_STAGING_API_URL }}
401407
SVELTEKIT_UPLOAD_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
@@ -417,6 +423,7 @@ jobs:
417423
[
418424
"bundler-plugin-core",
419425
"nuxt-plugin",
426+
"remix-plugin",
420427
"rollup-plugin",
421428
"sveltekit-plugin",
422429
"vite-plugin",
@@ -482,6 +489,7 @@ jobs:
482489
[
483490
"bundler-plugin-core",
484491
"nuxt-plugin",
492+
"remix-plugin",
485493
"rollup-plugin",
486494
"sveltekit-plugin",
487495
"vite-plugin",

examples/remix/.eslintrc.cjs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
ignorePatterns: ["!**/.server", "!**/.client"],
23+
24+
// Base config
25+
extends: ["eslint:recommended"],
26+
27+
overrides: [
28+
// React
29+
{
30+
files: ["**/*.{js,jsx,ts,tsx}"],
31+
plugins: ["react", "jsx-a11y"],
32+
extends: [
33+
"plugin:react/recommended",
34+
"plugin:react/jsx-runtime",
35+
"plugin:react-hooks/recommended",
36+
"plugin:jsx-a11y/recommended",
37+
],
38+
settings: {
39+
react: {
40+
version: "detect",
41+
},
42+
formComponents: ["Form"],
43+
linkComponents: [
44+
{ name: "Link", linkAttribute: "to" },
45+
{ name: "NavLink", linkAttribute: "to" },
46+
],
47+
"import/resolver": {
48+
typescript: {},
49+
},
50+
},
51+
},
52+
53+
// Typescript
54+
{
55+
files: ["**/*.{ts,tsx}"],
56+
plugins: ["@typescript-eslint", "import"],
57+
parser: "@typescript-eslint/parser",
58+
settings: {
59+
"import/internal-regex": "^~/",
60+
"import/resolver": {
61+
node: {
62+
extensions: [".ts", ".tsx"],
63+
},
64+
typescript: {
65+
alwaysTryTypes: true,
66+
},
67+
},
68+
},
69+
extends: [
70+
"plugin:@typescript-eslint/recommended",
71+
"plugin:import/recommended",
72+
"plugin:import/typescript",
73+
],
74+
},
75+
76+
// Node
77+
{
78+
files: [".eslintrc.cjs"],
79+
env: {
80+
node: true,
81+
},
82+
},
83+
],
84+
};

examples/remix/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env

examples/remix/README.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Welcome to Remix!
2+
3+
- 📖 [Remix docs](https://remix.run/docs)
4+
5+
## Development
6+
7+
Run the dev server:
8+
9+
```shellscript
10+
npm run dev
11+
```
12+
13+
## Deployment
14+
15+
First, build your app for production:
16+
17+
```sh
18+
npm run build
19+
```
20+
21+
Then run the app in production mode:
22+
23+
```sh
24+
npm start
25+
```
26+
27+
Now you'll need to pick a host to deploy it to.
28+
29+
### DIY
30+
31+
If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.
32+
33+
Make sure to deploy the output of `npm run build`
34+
35+
- `build/server`
36+
- `build/client`
37+
38+
## Styling
39+
40+
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.

examples/remix/app/entry.client.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 } from "@remix-run/react";
8+
import { startTransition, StrictMode } from "react";
9+
import { hydrateRoot } from "react-dom/client";
10+
11+
startTransition(() => {
12+
hydrateRoot(
13+
document,
14+
<StrictMode>
15+
<RemixBrowser />
16+
</StrictMode>,
17+
);
18+
});

examples/remix/app/entry.server.tsx

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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 { RemixServer } from "@remix-run/react";
12+
import { isbot } from "isbot";
13+
import { renderToPipeableStream } from "react-dom/server";
14+
15+
const ABORT_DELAY = 5_000;
16+
17+
export default function handleRequest(
18+
request: Request,
19+
responseStatusCode: number,
20+
responseHeaders: Headers,
21+
remixContext: EntryContext,
22+
// This is ignored so we can keep it in the template for visibility. Feel
23+
// free to delete this parameter in your app if you're not using it!
24+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
25+
loadContext: AppLoadContext,
26+
) {
27+
return isbot(request.headers.get("user-agent") || "")
28+
? handleBotRequest(
29+
request,
30+
responseStatusCode,
31+
responseHeaders,
32+
remixContext,
33+
)
34+
: handleBrowserRequest(
35+
request,
36+
responseStatusCode,
37+
responseHeaders,
38+
remixContext,
39+
);
40+
}
41+
42+
function handleBotRequest(
43+
request: Request,
44+
responseStatusCode: number,
45+
responseHeaders: Headers,
46+
remixContext: EntryContext,
47+
) {
48+
return new Promise((resolve, reject) => {
49+
let shellRendered = false;
50+
const { pipe, abort } = renderToPipeableStream(
51+
<RemixServer
52+
context={remixContext}
53+
url={request.url}
54+
abortDelay={ABORT_DELAY}
55+
/>,
56+
{
57+
onAllReady() {
58+
shellRendered = true;
59+
const body = new PassThrough();
60+
const stream = createReadableStreamFromReadable(body);
61+
62+
responseHeaders.set("Content-Type", "text/html");
63+
64+
resolve(
65+
new Response(stream, {
66+
headers: responseHeaders,
67+
status: responseStatusCode,
68+
}),
69+
);
70+
71+
pipe(body);
72+
},
73+
onShellError(error: unknown) {
74+
reject(error);
75+
},
76+
onError(error: unknown) {
77+
responseStatusCode = 500;
78+
// Log streaming rendering errors from inside the shell. Don't log
79+
// errors encountered during initial shell rendering since they'll
80+
// reject and get logged in handleDocumentRequest.
81+
if (shellRendered) {
82+
console.error(error);
83+
}
84+
},
85+
},
86+
);
87+
88+
setTimeout(abort, ABORT_DELAY);
89+
});
90+
}
91+
92+
function handleBrowserRequest(
93+
request: Request,
94+
responseStatusCode: number,
95+
responseHeaders: Headers,
96+
remixContext: EntryContext,
97+
) {
98+
return new Promise((resolve, reject) => {
99+
let shellRendered = false;
100+
const { pipe, abort } = renderToPipeableStream(
101+
<RemixServer
102+
context={remixContext}
103+
url={request.url}
104+
abortDelay={ABORT_DELAY}
105+
/>,
106+
{
107+
onShellReady() {
108+
shellRendered = true;
109+
const body = new PassThrough();
110+
const stream = createReadableStreamFromReadable(body);
111+
112+
responseHeaders.set("Content-Type", "text/html");
113+
114+
resolve(
115+
new Response(stream, {
116+
headers: responseHeaders,
117+
status: responseStatusCode,
118+
}),
119+
);
120+
121+
pipe(body);
122+
},
123+
onShellError(error: unknown) {
124+
reject(error);
125+
},
126+
onError(error: unknown) {
127+
responseStatusCode = 500;
128+
// Log streaming rendering errors from inside the shell. Don't log
129+
// errors encountered during initial shell rendering since they'll
130+
// reject and get logged in handleDocumentRequest.
131+
if (shellRendered) {
132+
console.error(error);
133+
}
134+
},
135+
},
136+
);
137+
138+
setTimeout(abort, ABORT_DELAY);
139+
});
140+
}

0 commit comments

Comments
 (0)