Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update to remix vite #519

Merged
merged 54 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2f98d69
chore: integrate remix vite (wip)
hi-ogawa Nov 12, 2023
6005202
fix: workaround uno.css infinite update
hi-ogawa Nov 12, 2023
048925e
fix: workaround "handle" skip HMR
hi-ogawa Nov 12, 2023
8883f0e
chore: tweak script
hi-ogawa Nov 12, 2023
2d23d28
fix: fix FOUC
hi-ogawa Nov 12, 2023
49bc14a
chore: comment
hi-ogawa Nov 12, 2023
5ee678f
chore: disable remix plugin on vitest
hi-ogawa Nov 12, 2023
5b7802d
chore: workaround knexfile dirname
hi-ogawa Nov 12, 2023
c865b95
chore: update build script
hi-ogawa Nov 12, 2023
ceaf48a
fix: more build workaround
hi-ogawa Nov 12, 2023
c356369
chore: workaround cjs
hi-ogawa Nov 12, 2023
2371adf
chore: struggle assets dir (wip)
hi-ogawa Nov 12, 2023
a16bfb2
chore: lint
hi-ogawa Nov 12, 2023
1fab0df
chore: disable coverage for e2e temporary
hi-ogawa Nov 12, 2023
7cfcd10
fix: inject css manually for prod
hi-ogawa Nov 12, 2023
20cff61
refactor: simplify knexfile
hi-ogawa Nov 12, 2023
ed834d4
chore: lint
hi-ogawa Nov 12, 2023
e317128
chore: one more knexfile workaround
hi-ogawa Nov 12, 2023
ed50bce
fix: patch `skipEnvCheck`
hi-ogawa Nov 12, 2023
6a36933
test: waitForHydration for e2e
hi-ogawa Nov 12, 2023
96a210f
refactor: use copyPublicDir = false
hi-ogawa Nov 17, 2023
833bc14
chore(deps): remix 2.3.0 and vite 5.0.0
hi-ogawa Nov 19, 2023
ea752c2
chore: remove unused
hi-ogawa Nov 19, 2023
fc7664a
chore: remove obsolete workaround
hi-ogawa Nov 19, 2023
0a940b2
ci: add waitForHydration to e2e
hi-ogawa Nov 19, 2023
8c42339
test: more waitForHydration
hi-ogawa Nov 19, 2023
08581eb
test: more ci struggle
hi-ogawa Nov 19, 2023
0ac381f
ci: disable coverage
hi-ogawa Nov 19, 2023
0217c8a
chore: esm
hi-ogawa Nov 19, 2023
f5f61d4
Revert "chore: esm"
hi-ogawa Nov 19, 2023
f376d43
chore(deps): update react-query (TODO: this should be done in a separ…
hi-ogawa Nov 19, 2023
7219dfe
Revert "chore(deps): update react-query (TODO: this should be done in…
hi-ogawa Nov 19, 2023
198a2b3
chore(deps): update unocss
hi-ogawa Nov 20, 2023
302e253
chore: silence react-query "use client" warning
hi-ogawa Nov 20, 2023
8e0e87d
chore: minify vercel serverless
hi-ogawa Nov 20, 2023
5e6efb9
chore: add vite.config.ui.ts
hi-ogawa Nov 20, 2023
21dc151
chore: HMR for "handle" export
hi-ogawa Nov 20, 2023
0c7d3d2
chore: update remix nightly (no more patch!)
hi-ogawa Nov 29, 2023
11e2f54
refactor: simplify config
hi-ogawa Nov 29, 2023
54752f9
chore: minor
hi-ogawa Nov 29, 2023
093171a
refactor: minor
hi-ogawa Nov 29, 2023
bad35dd
chore: tree-shake react-query-devtools
hi-ogawa Nov 30, 2023
3e628d0
chore: obsolete comment
hi-ogawa Dec 1, 2023
8c70605
chore: comment
hi-ogawa Dec 1, 2023
bdd19e0
chore: replace @vavite/connect with vite-plugin-ssr-middleware
hi-ogawa Dec 2, 2023
e580f93
chore: use `@hattip/adapter-node/native-fetch`
hi-ogawa Dec 4, 2023
79ced34
chore: unused deps
hi-ogawa Dec 4, 2023
b59fe29
chore: update @vitejs/plugin-react
hi-ogawa Dec 4, 2023
20c7570
chore: update opentelemetry
hi-ogawa Dec 4, 2023
73a438a
chore: move vitest.config to vite.config
hi-ogawa Dec 7, 2023
7a53a10
chore: install @vitest/ui
hi-ogawa Dec 11, 2023
098bd68
chore: more ignoredFilePatterns
hi-ogawa Dec 25, 2023
a8d8bea
chore: simplify FOUC hack
hi-ogawa Dec 25, 2023
1e8c36f
fix: fix vercel cache header
hi-ogawa Dec 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ jobs:
- if: steps.cache-playwright.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
- run: make docker/up db/reset/test db/seed-download
- run: pnpm test-e2e-coverage --retries=3
# TODO: fix coverage
# - run: pnpm test-e2e-coverage --retries=3
- run: pnpm test-e2e --retries=3
- run: rm -rf coverage/e2e-client/tmp coverage/e2e-server/tmp
- uses: actions/upload-artifact@v3
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
/.env.production.sh
/coverage
/test-results
dist
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/misc/ytsub-v2/data-v2.json
/coverage
.vercel
dist
3 changes: 1 addition & 2 deletions app/db/drizzle-client.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,7 @@ export async function dbGetMigrationStatus() {
const rows = await db.select().from(T.knex_migrations);

const fs = await import("fs");
const config = knexfile();
let files = await fs.promises.readdir(config.migrations.directory);
let files = await fs.promises.readdir("app/db/migrations");
files = files.filter((f) => f.match(/\.(js|ts)$/));
files.sort();

Expand Down
5 changes: 2 additions & 3 deletions app/db/knexfile.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from "node:path";
import type { Knex } from "knex";
import { initializeConfigServer, serverConfig } from "../utils/config";

Expand All @@ -18,8 +17,8 @@ export default function knexfile() {
timezone: "+00:00", // planetscale and development mysql image have UTC localtime
},
migrations: {
directory: path.join(__dirname, "migrations"),
stub: path.join(__dirname, "__migration-stub.ts"),
directory: "migrations",
stub: "__migration-stub.ts",
},
} satisfies Knex.Config;
}
20 changes: 19 additions & 1 deletion app/e2e/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Page, expect } from "@playwright/test";
import { E, T, db } from "../db/drizzle-client.server";
import type { Email } from "../utils/email-utils";
import { test } from "./coverage";
import { useUserE2E } from "./helper";
import { useUserE2E, waitForHydration } from "./helper";

test("/users/register success", async ({ page }) => {
await page.goto("/");
Expand All @@ -15,6 +15,7 @@ test("/users/register success", async ({ page }) => {
// navigate to register
await page.locator("data-test=signin-form >> text=Register").click();
await expect(page).toHaveURL("/users/register");
await waitForHydration(page);

// submit form
// prettier-ignore
Expand All @@ -33,6 +34,8 @@ test("/users/register success", async ({ page }) => {

test("/users/register error", async ({ page }) => {
await page.goto("/users/register");
await waitForHydration(page);

await page.getByLabel("Username").fill("hello");
await page.getByLabel("Password", { exact: true }).fill("hi");
await page.getByLabel("Password confirmation").fill("hi");
Expand All @@ -57,6 +60,7 @@ test.describe("/users/signin", () => {
// navigate to signin
await page.locator("header >> data-test=login-icon").click();
await expect(page).toHaveURL("/users/signin");
await waitForHydration(page);

// submit form
await page.locator('input[name="username"]').fill(user.data.username);
Expand All @@ -79,6 +83,8 @@ test.describe("/users/signin", () => {

test("invalid credentials", async ({ page }) => {
await page.goto("/users/signin");
await waitForHydration(page);

await page.getByLabel("Username").fill(user.data.username);
await page.getByLabel("Password").fill("asdfjkl;asdf-wrong");
await page.getByRole("button", { name: "Sign in" }).click();
Expand All @@ -94,6 +100,7 @@ test.describe("/users/me", () => {
test("with-session", async ({ page }) => {
await user.signin(page);
await page.goto("/users/me");
await waitForHydration(page);

// check user data is loaded
await expect(page.locator("data-test=me-username")).toHaveValue(
Expand Down Expand Up @@ -143,6 +150,7 @@ test.describe("/users/signout", () => {
test("basic", async ({ page }) => {
await signin(page);
await page.goto("/");
await waitForHydration(page);

// Signout from top menu
await page.locator('[data-test="user-menu"]').click();
Expand All @@ -159,6 +167,7 @@ test.describe("change email", () => {
test("basic", async ({ page }) => {
await user.signin(page);
await page.goto("/users/me");
await waitForHydration(page);

const newEmail = "[email protected]";
await page.getByRole("button", { name: "Change email" }).click();
Expand Down Expand Up @@ -226,6 +235,8 @@ test.describe("reset password", () => {
// trigger reset password from account
await user.signin(page);
await page.goto("/users/me");
await waitForHydration(page);

await page.getByRole("button", { name: "Reset password" }).click();
await page
.getByText("Please check your email to reset your password")
Expand All @@ -237,13 +248,17 @@ test.describe("reset password", () => {
// submit new password
const newPassword = "asdfjkl;";
await page.goto(url);
await waitForHydration(page);

await page.getByLabel("Password", { exact: true }).fill(newPassword);
await page.getByLabel("Password confirmation").fill(newPassword);
await page.getByRole("button", { name: "Submit" }).click();
await page.getByText("Successfully reset your password").click();

// cannot use a same link
await page.goto(url);
await waitForHydration(page);

await page.getByLabel("Password", { exact: true }).fill(newPassword);
await page.getByLabel("Password confirmation").fill(newPassword);
await page.getByRole("button", { name: "Submit" }).click();
Expand All @@ -253,6 +268,7 @@ test.describe("reset password", () => {
test("forgot password", async ({ page }) => {
// submit email from "forgot password" page
await page.goto("/users/signin");
await waitForHydration(page);
await page.getByRole("link", { name: "Forgot your password?" }).click();
await page.getByLabel("Email").fill(userEmail);
await page.getByRole("button", { name: "Submit" }).click();
Expand All @@ -266,13 +282,15 @@ test.describe("reset password", () => {
// submit new password
const newPassword = "12345678";
await page.goto(url);
await waitForHydration(page);
await page.getByLabel("Password", { exact: true }).fill(newPassword);
await page.getByLabel("Password confirmation").fill(newPassword);
await page.getByRole("button", { name: "Submit" }).click();
await page.getByText("Successfully reset your password").click();

// login with new password
await page.locator('[data-test="login-icon"]').click();
await waitForHydration(page);
await page.getByLabel("Username").fill(user.data.username);
await page.getByLabel("Password").fill(newPassword);
await page.getByRole("button", { name: "Sign in" }).click();
Expand Down
4 changes: 4 additions & 0 deletions app/e2e/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ export function useUserE2E(
},
};
}

export async function waitForHydration(page: Page) {
await page.locator("#root.hydrated").waitFor({ state: "attached" });
}
1 change: 1 addition & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function main() {
const el = document.getElementById("root");
tinyassert(el);
hydrateRoot(el, <RemixBrowser />);
el.classList.add("hydrated");
}

main();
23 changes: 21 additions & 2 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { tinyassert } from "@hiogawa/utils";
import { viteDevServer } from "@hiogawa/vite-import-dev-server/runtime";
import { RemixServer } from "@remix-run/react";
import type { HandleDocumentRequestFunction } from "@remix-run/server-runtime";
import { renderToString } from "react-dom/server";
import { renderToDocument } from "./server/document";
import { wrapTraceAsyncSimple } from "./utils/opentelemetry-utils";

const handleDocumentRequest: HandleDocumentRequestFunction = (
const handleDocumentRequest: HandleDocumentRequestFunction = async (
request,
responseStatusCode,
responseHeaders,
Expand All @@ -14,7 +16,24 @@ const handleDocumentRequest: HandleDocumentRequestFunction = (
const ssrHtml = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
const documentHtml = renderToDocument(ssrHtml);

let style: string;
if (import.meta.env.DEV) {
// inject CSS to quickly workaround FOUC during dev
// since vite/unocss will inject css on client via javascript later.
// this would essentially create a duplicate style,
// but that's not usually a problem for utility-class based styling.
tinyassert(viteDevServer);
const unocss = await viteDevServer.ssrLoadModule("virtual:uno.css");
style = `<style>${unocss["default"]}</style>`;
} else {
// since we don't use <Links />, we have to inject unocss output manually.
const root = remixContext.manifest.routes["root"];
tinyassert(root && root.css && root.css[0]);
style = `<link rel="stylesheet" href="${root.css[0]}">`;
}

const documentHtml = await renderToDocument(ssrHtml, style);
responseHeaders.set("content-type", "text/html");
return new Response(documentHtml, {
status: responseStatusCode,
Expand Down
8 changes: 5 additions & 3 deletions app/misc/entry-hattip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { once } from "@hiogawa/utils";
import { createLoggerHandler } from "@hiogawa/utils-hattip";
import { SpanKind } from "@opentelemetry/api";
import { SemanticAttributes } from "@opentelemetry/semantic-conventions";
import * as build from "@remix-run/dev/server-build";
import { createRequestHandler } from "@remix-run/server-runtime";
import { requestContextHandler } from "../server/request-context";
import { rpcHandler } from "../trpc/server";
import { pathToRegExp } from "../utils/misc";
import { traceAsync } from "../utils/opentelemetry-utils";
import { initializeServer } from "./initialize-server";

// @ts-ignore isort-ignore
import * as remixViteBuild from "virtual:server-entry";
const build = remixViteBuild as typeof import("@remix-run/dev/server-build");

// based on https://github.com/hi-ogawa/vite-fullstack-example/blob/92649f99b041820ec86650c99cfcd49a72e79f71/src/server/hattip.ts#L16-L28

export function createHattipEntry() {
Expand All @@ -29,8 +32,7 @@ export function createHattipEntry() {
//

function createRemixHandler(): RequestHandler {
const mode =
process.env.NODE_ENV === "production" ? "production" : "development";
const mode = import.meta.env.DEV ? "development" : "production";
const remixHandler = createRequestHandler(build, mode);
return (ctx) => remixHandler(ctx.request);
}
Expand Down
1 change: 1 addition & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "virtual:uno.css";
import { FloatingTree } from "@floating-ui/react";
import { useTinyForm } from "@hiogawa/tiny-form/dist/react";
import { useTinyProgress } from "@hiogawa/tiny-progress/dist/react";
Expand Down
4 changes: 2 additions & 2 deletions app/server/document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { injectPublicConfigScript, publicConfig } from "../utils/config-public";
// since we don't currently use remix's <Meta /> or <Links /> convention,
// we can render static document html only on server, which is probably common ssr practice.

export function renderToDocument(ssrHtml: string) {
export async function renderToDocument(ssrHtml: string, style: string) {
// syntax highlight by https://github.com/mjbvz/vscode-comment-tagged-templates/
return /* html */ `
<!DOCTYPE html>
Expand All @@ -19,7 +19,6 @@ export function renderToDocument(ssrHtml: string) {
content="width=device-width, height=device-height, initial-scale=1.0"
/>
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href=${require("../../build/css/index.css")} />
<link
rel="icon"
type="image/svg+xml"
Expand All @@ -32,6 +31,7 @@ export function renderToDocument(ssrHtml: string) {
height: 100%;
}
</style>
${style}
${generateThemeScript({ storageKey: "ytsub:theme" })}
${injectPublicConfigScript()}
</head>
Expand Down
4 changes: 2 additions & 2 deletions app/utils/loader-utils.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoaderArgs, json, redirect } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, json, redirect } from "@remix-run/server-runtime";
import { $R } from "../misc/routes";
import { ctx_currentUser } from "../server/request-context/session";
import { ctx_get } from "../server/request-context/storage";
Expand All @@ -9,7 +9,7 @@ import { JSON_EXTRA } from "./json-extra";
// - custom json serializer by default
export function wrapLoader(loader: () => unknown) {
// make it partial for slight convenience of unit test
return async (args?: Partial<LoaderArgs>) => {
return async (args?: Partial<LoaderFunctionArgs>) => {
ctx_get().params = args?.params ?? {};
const res = await loader();
return res instanceof Response ? res : json(JSON_EXTRA.serialize(res));
Expand Down
27 changes: 14 additions & 13 deletions misc/vercel/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@ set -eu -o pipefail
# index.js = (remix-outdir)/server/index.js
#

this_dir="$(dirname "${BASH_SOURCE[0]}")"

# cleanup
rm -rf build/remix/production
rm -rf build/css
rm -rf .vercel/output
mkdir -p .vercel/output/functions/index.func
mkdir -p .vercel/output

# css
pnpm build:css
mkdir -p .vercel/output/functions/index.func

# remix build with custom server entry
NODE_ENV=production BUILD_VERCEL=1 npx remix build
# serverless
mkdir -p .vercel/output/functions/index.func
cp "$this_dir/.vc-config.json" .vercel/output/functions/index.func/.vc-config.json
npx esbuild dist/server/index.js \
--outfile=.vercel/output/functions/index.func/index.js \
--metafile=dist/server/esbuild-metafile.json \
--bundle --format=cjs --platform=node \
--external:node:async_hooks

# config.json
cp misc/vercel/config.json .vercel/output/config.json
cp "$this_dir/config.json" .vercel/output/config.json

# static
cp -r ./build/remix/production/public .vercel/output/static
cp -r dist/client .vercel/output/static
cp ./public/* .vercel/output/static

# serverless
cp build/remix/production/server/index.js .vercel/output/functions/index.func
cp misc/vercel/.vc-config.json .vercel/output/functions/index.func/.vc-config.json

# output server size
echo "* Serverless files"
ls -lhA .vercel/output/functions/index.func
Loading
Loading