Skip to content

Commit

Permalink
feat: Upgrade to Remix 2
Browse files Browse the repository at this point in the history
- Switch to standard functions for action/loader
- Flatten routes
- Various fixes for dependency upgrades
  • Loading branch information
dcramer committed Mar 25, 2024
1 parent 4a23b07 commit 7f403d5
Show file tree
Hide file tree
Showing 71 changed files with 4,140 additions and 5,136 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
31 changes: 1 addition & 30 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,3 @@
{
"files.exclude": {
".vscode/tags": true,
"public": true,
"build": true,
"node_modules": true
},
"files.trimTrailingWhitespace": false,
"files.trimFinalNewlines": false,
"files.insertFinalNewline": true,
"[typescript]": {
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[yaml]": {
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[json]": {
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"files.associations": {
"*.yaml": "home-assistant"
}
"editor.formatOnSave": true
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18 as base
FROM node:20 as base

# set for base and all layer that inherit from it
ENV NODE_ENV production
Expand Down
4 changes: 2 additions & 2 deletions app/components/loading-indicator.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useTransition } from "@remix-run/react";
import { useLocation } from "@remix-run/react";
import type { ReactElement } from "react";
import { useEffect, useRef } from "react";

// https://edmund.dev/articles/setting-up-a-global-loading-indicator-in-remix
function useProgress() {
const el = useRef<HTMLDivElement>(null);
const timeout = useRef<NodeJS.Timeout>();
const { location } = useTransition();
const { location } = useLocation();

useEffect(() => {
if (!location || !el.current) {
Expand Down
4 changes: 2 additions & 2 deletions app/components/paginated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ const Paginated: React.FC<PaginatedProps<any>> = function Paginated({
const pagination = (
<ButtonGroup align="center">
<Button
as={Link}
as={prevCursor ? Link : "button"}

Check warning on line 22 in app/components/paginated.tsx

View check run for this annotation

Codecov / codecov/patch

app/components/paginated.tsx#L22

Added line #L22 was not covered by tests
to={prevCursor ? `?cursor=${prevCursor}` : undefined}
disabled={!prevCursor}
>
Prev Page
</Button>
<Button
as={Link}
as={nextCursor ? Link : "button"}

Check warning on line 29 in app/components/paginated.tsx

View check run for this annotation

Codecov / codecov/patch

app/components/paginated.tsx#L29

Added line #L29 was not covered by tests
to={nextCursor ? `?cursor=${nextCursor}` : undefined}
disabled={!nextCursor}
>
Expand Down
11 changes: 9 additions & 2 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ Sentry.init({
useMatches,
),
}),
new Sentry.Replay()
new Sentry.Replay(),
],

replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0
replaysOnErrorSampleRate: 1.0,
});

startTransition(() => {
Expand All @@ -33,3 +34,9 @@ startTransition(() => {
</StrictMode>,
);
});

if (process.env.NODE_ENV === "development") {
import("@spotlightjs/spotlight").then((Spotlight) =>
Spotlight.init({ injectImmediately: true }),
);
}
106 changes: 32 additions & 74 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,46 @@
import { PassThrough } from "node:stream";

import type { EntryContext } from "@remix-run/node";
import { Response } from "@remix-run/node";
import { PassThrough } from "stream";
import {
createReadableStreamFromReadable,
type LoaderFunctionArgs,
type ActionFunctionArgs,
type HandleDocumentRequestFunction,
} 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;

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) => {
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onAllReady() {
const body = new PassThrough();
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>;

responseHeaders.set("Content-Type", "text/html");
export default async function handleRequest(...args: DocRequestArgs) {
const [request, responseStatusCode, responseHeaders, remixContext] = args;

resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
}),
);
const callbackName = isbot(request.headers.get("user-agent"))
? "onAllReady"
: "onShellReady";

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
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(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
[callbackName]: () => {
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(body, {
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
status: didError ? 500 : responseStatusCode,
}),
);

Expand All @@ -103,12 +50,23 @@ function handleBrowserRequest(
reject(error);
},
onError(error: unknown) {
didError = true;
console.error(error);
responseStatusCode = 500;
},
},
);

setTimeout(abort, ABORT_DELAY);
});
}

export function handleError(
error: unknown,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
if (error instanceof Error) {
Sentry.captureRemixServerException(error, "remix.server", request);
} else {
Sentry.captureException(error);
}
}
6 changes: 3 additions & 3 deletions app/lib/summarize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export default (content: string, maxLength = 256): string => {
ALLOWED_TAGS: ["p", "blockquote", "#text", "strong", "b", "em", "i", "a"],
KEEP_CONTENT: false,
});
const sum = sanitize(contentBlocks, {
ALLOWED_TAGS: [],
}).replace(/^[\s\n]+|[\s\n]+$/g, "");

const doc = new DOMParser().parseFromString(contentBlocks, "text/html");
const sum = (doc.body.textContent || "").replace(/^[\s\n]+|[\s\n]+$/g, "");
if (sum.length > maxLength)
return sum.substring(0, maxLength - 3).split("\n")[0] + "...";
return sum;
Expand Down
2 changes: 1 addition & 1 deletion app/lib/upload-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Storage } from "@google-cloud/storage";
import type { FileUploadHandlerOptions } from "@remix-run/node/dist/upload/fileUploadHandler";
import type { UploadHandler } from "@remix-run/node";
import path from "path";
import cuid from "cuid";
import { createId as cuid } from "@paralleldrive/cuid2";

type UploadHandlerOptions = {
namespace: string;
Expand Down
14 changes: 10 additions & 4 deletions app/models/user.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,20 @@ export async function updateUser({

// admin only fields
if (user.admin) {
if (admin !== undefined) data.admin = !!admin;
if (canPostRestricted !== undefined)
if (admin !== undefined && admin !== user.admin) data.admin = !!admin;
if (
canPostRestricted !== undefined &&
canPostRestricted !== user.canPostRestricted
)

Check warning on line 202 in app/models/user.server.ts

View check run for this annotation

Codecov / codecov/patch

app/models/user.server.ts#L198-L202

Added lines #L198 - L202 were not covered by tests
data.canPostRestricted = !!canPostRestricted;
}

if (name !== undefined) data.name = name;
if (name !== undefined && name !== user.name) data.name = name;
if (picture !== undefined) data.picture = picture;
if (notifyReplies !== undefined) data.notifyReplies = !!notifyReplies;
if (notifyReplies !== undefined && notifyReplies !== user.notifyReplies)
data.notifyReplies = !!notifyReplies;

console.log({ data });

return await prisma.user.update({
where: {
Expand Down
45 changes: 26 additions & 19 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,23 @@ export const loader: LoaderFunction = async ({ context: { user } }) => {
// }
// }

const loaderData: LoaderData = {
return json({
user,
config,
};

if (user) {
loaderData.categoryList = await getCategoryList({
userId: user?.id,
includeRestricted: true,
});

loaderData.recentPostList = await getPostList({
userId: user?.id,
published: true,
limit: 3,
});
}

return json<LoaderData>(loaderData);
categoryList: user
? await getCategoryList({
userId: user.id,
includeRestricted: true,
})
: null,
recentPostList: user
? await getPostList({
userId: user.id,
published: true,
limit: 3,
})
: null,
});
};

export function ErrorBoundary() {
Expand Down Expand Up @@ -110,10 +108,19 @@ function App() {
recentPostList,
config = {},
...data
} = useLoaderData<typeof loader>();
} = useLoaderData<LoaderData>();

const [showSidebar, setShowSidebar] = useState(false);

setUser(user);
if (user) {
setUser({
id: `${user.id}`,
name: user.name,
email: user.email,
});
} else {
setUser(null);
}

const handleSidebar = () => {
setShowSidebar(!showSidebar);
Expand Down
13 changes: 1 addition & 12 deletions app/routes/admin/index.tsx → app/routes/admin._index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import type { LoaderFunction } from "@remix-run/node";
import { Outlet, useLoaderData } from "@remix-run/react";
import { Outlet } from "@remix-run/react";
import Link from "~/components/link";

import { requireAdmin } from "~/services/auth.server";

export const loader: LoaderFunction = async ({ request, context }) => {
await requireAdmin(request, context);

return null;
};

export default function Index() {
useLoaderData();

return (
<div className="mb-6">
<h1>Admin</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Fixtures from "~/lib/test/fixtures";
import { buildRequest } from "~/lib/test/request";
import { prisma } from "~/services/db.server";

import { action, loader } from "./$categoryId";
import { action, loader } from "./admin.categories.$categoryId";

const EMOJI = "🦰";

Expand Down

0 comments on commit 7f403d5

Please sign in to comment.