diff --git a/app/root.tsx b/app/root.tsx index 8a8de7c..c9dbd86 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,3 @@ -import { cssBundleHref } from "@remix-run/css-bundle"; import type { LinksFunction } from "@remix-run/node"; import { Links, @@ -8,12 +7,9 @@ import { Scripts, ScrollRestoration, } from "@remix-run/react"; -import styles from "./tailwind.css"; -export const links: LinksFunction = () => [ - ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), - { rel: "stylesheet", href: styles }, -]; +//css should be imported as a side effect for vite +import "./tailwind.css"; export default function App() { return ( @@ -27,8 +23,8 @@ export default function App() { - + ); diff --git a/remix.env.d.ts b/env.d.ts similarity index 50% rename from remix.env.d.ts rename to env.d.ts index dcf8c45..78ed234 100644 --- a/remix.env.d.ts +++ b/env.d.ts @@ -1,2 +1,2 @@ -/// /// +/// diff --git a/package.json b/package.json index c34bc96..298670b 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,16 @@ "type": "module", "scripts": { "build": "run-p build:*", - "build:remix": "remix build", + "build:remix": "vite build && vite build --ssr", "build:server": "esbuild --platform=node --format=esm ./server.ts --outdir=./", - "dev": "remix dev -c \"npm run dev:server\" --manual", - "dev:server": "tsx watch --clear-screen=false --ignore 'app/**' --ignore 'build/**' --ignore 'node_modules/**' --inspect ./server.ts", - "start": "cross-env NODE_ENV=production node server.js", + "dev": "node --loader ts-node/esm --watch-path ./server.ts --watch ./server.ts", + "start": "cross-env NODE_ENV=production node server.js", "typecheck": "tsc" }, "dependencies": { - "@remix-run/css-bundle": "*", - "@remix-run/express": "*", - "@remix-run/node": "*", - "@remix-run/react": "*", + "@remix-run/express": "^2.2.0", + "@remix-run/node": "^2.2.0", + "@remix-run/react": "^2.2.0", "compression": "^1.7.4", "cross-env": "^7.0.3", "express": "^4.18.2", @@ -26,8 +24,9 @@ "source-map-support": "^0.5.21" }, "devDependencies": { - "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", + "@remix-run/dev": "^2.2.0", + "@remix-run/eslint-config": "^2.2.0", + "@swc/core": "1.3.82", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", "@types/morgan": "^1.9.4", @@ -35,14 +34,14 @@ "@types/react-dom": "^18.0.11", "@types/source-map-support": "^0.5.6", "autoprefixer": "^10.4.16", - "chokidar": "^3.5.3", - "esbuild": "^0.19.4", "eslint": "^8.38.0", "npm-run-all": "^4.1.5", "postcss": "^8.4.31", "tailwindcss": "^3.3.3", - "tsx": "^3.14.0", - "typescript": "^5.0.4" + "ts-node": "^10.9.1", + "typescript": "^5.0.4", + "vite": "^4.5.0", + "vite-tsconfig-paths": "^4.2.1" }, "engines": { "node": ">=18" diff --git a/postcss.config.js b/postcss.config.js index 7738160..2aa7205 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,6 @@ export default { plugins: { + tailwindcss: {}, autoprefixer: {}, }, }; diff --git a/remix.config.js b/remix.config.js deleted file mode 100644 index f116c82..0000000 --- a/remix.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('@remix-run/dev').AppConfig} */ -export default { - ignoredRouteFiles: ["**/.*"], - // appDirectory: "app", - // assetsBuildDirectory: "public/build", - // serverBuildPath: "build/index.js", - // publicPath: "/build/", - serverModuleFormat: "esm", - tailwind: true, - postcss: true, -}; diff --git a/server.ts b/server.ts index 6dc49db..8b3f661 100644 --- a/server.ts +++ b/server.ts @@ -1,113 +1,58 @@ -import * as fs from "node:fs"; import express from "express"; import compression from "compression"; import morgan from "morgan"; -import { createRequestHandler, type RequestHandler } from "@remix-run/express"; -import { broadcastDevReady, installGlobals } from "@remix-run/node"; import sourceMapSupport from "source-map-support"; +import { + unstable_createViteServer, + unstable_loadViteServerBuild, +} from "@remix-run/dev"; +import { createRequestHandler } from "@remix-run/express"; +import { installGlobals } from "@remix-run/node"; + // patch in Remix runtime globals installGlobals(); sourceMapSupport.install(); -/** - * @typedef {import('@remix-run/node').ServerBuild} ServerBuild - */ -const BUILD_PATH = "./build/index.js"; -const WATCH_PATH = "./build/version.txt"; - -/** - * Initial build - * @type {ServerBuild} - */ -let build = await import(BUILD_PATH); - -// We'll make chokidar a dev dependency so it doesn't get bundled in production. -const chokidar = - process.env.NODE_ENV === "development" ? await import("chokidar") : null; - const app = express(); +// handle asset requests +let vite = + process.env.NODE_ENV === "production" + ? undefined + : await unstable_createViteServer(); + +if (vite) { + app.use(vite.middlewares); +} else { + app.use( + "/build", + express.static("public/build", { immutable: true, maxAge: "1y" }) + ); +} + app.use(compression()); // http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header app.disable("x-powered-by"); -// Remix fingerprints its assets so we can cache forever. -app.use( - "/build", - express.static("public/build", { immutable: true, maxAge: "1y" }) -); - // Everything else (like favicon.ico) is cached for an hour. You may want to be // more aggressive with this caching. app.use(express.static("public", { maxAge: "1h" })); app.use(morgan("tiny")); -// Check if the server is running in development mode and use the devBuild to reflect realtime changes in the codebase. +// handle SSR requests app.all( "*", - process.env.NODE_ENV === "development" - ? createDevRequestHandler() - : createRequestHandler({ - build, - mode: process.env.NODE_ENV, - }) + createRequestHandler({ + build: vite + ? () => unstable_loadViteServerBuild(vite) + : await import("./build/index.js"), + }) ); -const port = process.env.PORT || 3000; - -app.listen(port, async () => { - console.log(`Express server listening on port ${port}`); - - // send "ready" message to dev server - if (process.env.NODE_ENV === "development") { - await broadcastDevReady(build); - } -}); - -// Create a request handler that watches for changes to the server build during development. -function createDevRequestHandler(): RequestHandler { - async function handleServerUpdate() { - // 1. re-import the server build - build = await reimportServer(); - - // Add debugger to assist in v2 dev debugging - if (build?.assets === undefined) { - console.log(build.assets); - debugger; - } - - // 2. tell dev server that this app server is now up-to-date and ready - await broadcastDevReady(build); - } - - chokidar - ?.watch(WATCH_PATH, { ignoreInitial: true }) - .on("add", handleServerUpdate) - .on("change", handleServerUpdate); - - // wrap request handler to make sure its recreated with the latest build for every request - return async (req, res, next) => { - try { - return createRequestHandler({ - build, - mode: "development", - })(req, res, next); - } catch (error) { - next(error); - } - }; -} - -// ESM import cache busting -/** - * @type {() => Promise} - */ -async function reimportServer() { - const stat = fs.statSync(BUILD_PATH); - - // use a timestamp query parameter to bust the import cache - return import(BUILD_PATH + "?t=" + stat.mtimeMs); -} +const port = 3000; +app.listen(port, () => + console.log("Express server listening on http://localhost:" + port) +); diff --git a/tsconfig.json b/tsconfig.json index 80b321d..11afc05 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["env.d.ts", "**/*.ts", "**/*.tsx"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, @@ -22,5 +22,8 @@ // Remix takes care of building everything in `remix build`. "noEmit": true + }, + "ts-node": { + "swc": true } } diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..3a9564e --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,21 @@ +import { unstable_vitePlugin as remix } from "@remix-run/dev"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + optimizeDeps: { + include: ["react", "react-dom/client"], + }, + plugins: [ + remix({ + ignoredRouteFiles: ["**/.*"], + tailwind: true, + postcss: true, + // appDirectory: "app", + // assetsBuildDirectory: "public/build", + // publicPath: "/build/", + // serverBuildPath: "build/index.js", + }), + tsconfigPaths(), + ], +});