paint-brush
So erstellen Sie ein Monorepo mit Vite, Cloudflare, Remix, PNPM und Turborepo (kein Build-Schritt)von@josejaviasilis
Neue Geschichte

So erstellen Sie ein Monorepo mit Vite, Cloudflare, Remix, PNPM und Turborepo (kein Build-Schritt)

von Jose Javi Asilis19m2024/08/28
Read on Terminal Reader

Zu lang; Lesen

Dadurch wird ein Monorepo für Remix's Vite Cloduflare Deployments ohne Build-Schritt erstellt. Dadurch können Sie es auf mehrere Ziele erweitern
featured image - So erstellen Sie ein Monorepo mit Vite, Cloudflare, Remix, PNPM und Turborepo (kein Build-Schritt)
Jose Javi Asilis HackerNoon profile picture
0-item




Einführung

Ich brauchte eine Möglichkeit, Remix mit Vite und Cloudflare Workers-Pages mit minimaler Konfiguration zu verwenden.


Ich habe andere Repositories gesehen, wie zum Beispiel:


Es gab jedoch einige Einschränkungen:

  1. Ich wollte es nicht vorab erstellen, da ich die Repositories nicht mit weiteren Konfigurationsdateien vergiften wollte.


  2. Cloudflare Workers/Pages hat ein anderes Ziel. Es wurde schwierig, es mit tsup anzusprechen, da Pakete wie Postgres die Knotenabhängigkeiten beim Import in Remix beschädigten.


  3. Ich brauchte auch eine Möglichkeit, verschiedene Ziele zu nutzen (Remix-Cloudflare, Node/Bun).


Dennoch danke ich ihnen, da sie den Weg geebnet haben, der dies möglich gemacht hat!


Lesen Sie unbedingt den Abschnitt zu Fallstricke unten!

Folgen Sie mir in den sozialen Netzwerken!

Ich baue eine öffentliche automatisierte Testplattform auf, um diese 1 %-Fehler in der Produktion abzufangen.


Ich teile meine Fortschritte bei:


X/Twitter @javiasilis

LinkedIn @javiasilis

GitHub-Repository

Auf die vollständige Implementierung können Sie hier zugreifen.

Schritt für Schritt

Anforderungen

  1. NodeJS
  2. PNPM
  3. Docker (Optional – beispielsweise für lokale Datenbanken)


Obwohl Sie hier durch ein neues Mono-Repository geführt werden, ist es durchaus zulässig, ein vorhandenes in ein solches umzuwandeln.


Dies setzt auch voraus, dass Sie über einige Kenntnisse zu Mono-Repos verfügen.


Notiz:

  • „at root“ bezieht sich auf den Anfangspfad Ihres Monorepositorys. Für dieses Projekt wird es außerhalb der Verzeichnisse libs und „ packages liegen.

Turborepo installieren

Turborepo arbeitet auf den Arbeitsbereichen Ihres Paketmanagers, um die Skripte und Ausgaben Ihres Projekts zu verwalten (es kann Ihre Ausgabe sogar zwischenspeichern). Bisher ist es neben Rush (das ich nicht ausprobiert habe und nicht mag) das einzige funktionierende Mono-Repo-Tool.


NX verfügt nicht über die Vite-Unterstützung von Remix (zum Zeitpunkt des Schreibens dieses Artikels – 28. August 2024).


https://turbo.build/

 pnpm dlx create-turbo@latest

1. PNPM-Arbeitsbereiche konfigurieren

Wir werden die Arbeitsbereichsfunktionen von PNPM verwenden, um Abhängigkeiten zu verwalten.


Erstellen Sie in Ihrem Monorepo-Verzeichnis eine pnpm-workspace.yaml .


Fügen Sie darin hinzu:

 packages: - "apps/*" - "libs/*"


Dadurch wird pnpm mitgeteilt, dass alle Repositories in apps und libs liegen. Beachten Sie, dass die Verwendung von libs oder packages (wie Sie vielleicht schon anderswo gesehen haben) keine Rolle spielt.

2. Erstellen Sie eine leere Datei package.json im Stammverzeichnis des Projekts:

 pnpm init
 { "name": "@repo/main", "version": "1.0.0", "scripts": {}, "keywords": [], "author": "", "license": "ISC" }


Beachten Sie den name:@repo/main Dies zeigt uns, dass dies der Haupteintrag der Anwendung ist. Sie müssen keiner bestimmten Konvention folgen oder das Präfix @ verwenden. Benutzer verwenden es, um es von lokalen/Remote-Paketen zu unterscheiden oder um es einfacher zu machen, es in einer Organisation zu gruppieren.

3. Erstellen Sie die Datei turbo.json im Stammverzeichnis des Projekts:

 { "$schema": "https://turbo.build/schema.json", "tasks": { "build": {}, "dev": { "cache": false, "persistent": true }, "start": { "dependsOn": ["^build"], "persistent": true }, "preview": { "cache": false, "persistent": true }, "db:migrate": {} } }


Die Datei turbo.json teilt dem Turbo-Repository mit, wie unsere Befehle zu interpretieren sind. Alles, was im Schlüssel „ tasks steht, stimmt mit dem überein, was in der Datei „all package.json“ steht.


Beachten Sie, dass wir vier Befehle definieren. Diese entsprechen denen im Skriptabschnitt der package.json jedes Repositorys. Allerdings müssen nicht alle package.json diese Befehle implementieren.


Beispiel: Der dev Befehl wird von turbo dev ausgelöst und führt alle Pakete aus, deren dev in package.json gefunden wird. Wenn Sie es nicht in turbo einschließen, wird es nicht ausgeführt.

4. Erstellen Sie einen apps Ordner im Stammverzeichnis des Projekts

 mkdir apps

5. Erstellen Sie eine Remix-App im apps Ordner (oder verschieben Sie eine vorhandene)

 npx create-remix --template edmundhung/remix-worker-template


Wenn Sie aufgefordert werden Install any dependencies with npm sagen Sie „Nein“.


  • So sollte es aussehen

6. Benennen Sie den name der Datei package.json in @repo/my-remix-cloudflare-app (oder Ihren Namen) um.

 { - "name": "my-remix-cloudflare-app", + "name": "@repo/my-remix-cloudflare-app", "version": "1.0.0", "keywords": [], "author": "", "license": "ISC" }

7. Kopieren Sie die Abhängigkeiten und devDependencies von apps/<app>/package.json in die Datei package.json des Stammverzeichnisses

Z.B:

<Stamm>/Paket.json

 { "name": "@repo/main", "version": "1.0.0", "scripts": {}, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@markdoc/markdoc": "^0.4.0", "@remix-run/cloudflare": "^2.8.1", "@remix-run/cloudflare-pages": "^2.8.1", "@remix-run/react": "^2.8.1", "isbot": "^3.6.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20240222.0", "@octokit/types": "^12.6.0", "@playwright/test": "^1.42.1", "@remix-run/dev": "^2.8.1", "@remix-run/eslint-config": "^2.8.1", "@tailwindcss/typography": "^0.5.10", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", "autoprefixer": "^10.4.18", "concurrently": "^8.2.2", "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "husky": "^9.0.11", "lint-staged": "^15.2.2", "msw": "^2.2.3", "postcss": "^8.4.35", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.12", "rimraf": "^5.0.5", "tailwindcss": "^3.4.1", "typescript": "^5.4.2", "vite": "^5.1.5", "vite-tsconfig-paths": "^4.3.1", "wrangler": "^3.32.0" } }


8. Fügen Sie Turbo als devDependency auf der Stammebene hinzu (dadurch werden alle Remix-Pakete installiert).

Überprüfen Sie, ob turbo in den devDependencies von package.json enthalten ist. Wenn es nicht aufgeführt ist, führen Sie den folgenden Befehl aus:

 pnpm add turbo -D -w


Das Flag -w weist pnpm an, es im Stammverzeichnis des Arbeitsbereichs zu installieren.

9. Fügen Sie die folgenden Einträge zum Stammpaket package.json hinzu

  • Fügen Sie den dev Befehl zu scripts hinzu

  • Fügen Sie den packageManager zur Option hinzu


 { "name": "@repo/main", "version": "1.0.0", "scripts": { "dev": "turbo dev" }, "keywords": [], "author": "", "license": "ISC", "packageManager": "[email protected]", "dependencies": { // omitted for brevity }, "devDependencies": { // omitted for brevity } }

10. Überprüfen Sie, ob alles funktioniert, indem Sie pnpm dev ausführen

 pnpm dev

11. Erstellen Sie einen Libs-Ordner im Stammverzeichnis des Projekts. Fügen Sie config, db und utils hinzu:

 mkdir -p libs/config libs/db libs/utils

12. Fügen Sie für jedes Paket ein src/index.ts hinzu.

 touch libs/config/src/index.ts libs/db/src/index.ts libs/utils/src/index.ts
  • Die Datei index.ts wird zum Exportieren aller Pakete verwendet.
  • Wir verwenden den Ordner als Einstiegspunkt, um alles kompakt zu halten.
  • Dies ist eine Konvention und Sie können sich entscheiden, sie nicht zu befolgen.

13. Erstellen Sie eine leere Datei package.json und fügen Sie der Datei libs/config/package.json Folgendes hinzu:

 { "name": "@repo/config", "version": "1.0.0", "type": "module", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }

14. Machen Sie dasselbe für libs/db/package.json :

 { "name": "@repo/db", "version": "1.0.0", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }

15. Und libs/utils/package.json :

 { "name": "@repo/utils", "version": "1.0.0", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }


  • Wir geben das Feld „Exports“ an. Dies teilt anderen Repositories mit, woher das Paket importiert werden soll.
  • Wir geben das Feld „Name“ an. Dies wird verwendet, um das Paket zu installieren und es auf andere Repositories zu verweisen.

16. (DB) - Drizzle und Postgres hinzufügen

Hinweise:

  • Ich fange an, ORMs zu verachten. Ich habe über 10 Jahre damit verbracht, 6 verschiedene zu lernen, und dieses Wissen lässt sich nicht übertragen.

  • Sie haben Probleme, wenn neue Technologien auf den Markt kommen. Prisma unterstützt Cloudflare-Worker nicht von Haus aus.

  • Mit LLMs ist es einfacher als je zuvor, komplexe SQL-Abfragen zu schreiben.

  • Das Erlernen von SQL ist eine universelle Sprache und wird sich wahrscheinlich nicht ändern.


 pnpm add drizzle-orm drizle-kit --filter=@repo/db


Installieren Sie Postgres auf Arbeitsbereichsebene. Weitere Informationen finden Sie im Abschnitt „Fallstricke“ .

 pnma add postgres -w


Hinweise:

  • Das Flag --filter=@repo/db weist pnpm an, das Paket zum DB-Repository hinzuzufügen.

17. Fügen Sie dotenv zum Workspace Repository hinzu

 pnpm add dotenv -w

Hinweise

  • Das Flag -w weist pnpm an, es in der Stammdatei package.json zu installieren.

18. Fügen Sie das Konfigurationsprojekt zu allen Projekten hinzu.

 pnpm add @repo/config -r --filter=!@repo/config

Hinweise :

  • Das Flag -r weist pnpm an, das Paket zu allen Repositories hinzuzufügen.
  • Das Flag --filter=! weist pnpm an, das Konfigurations-Repository auszuschließen.
  • Beachten Sie das ! vor dem Paketnamen

19. (Optional) Funktionieren die obigen Befehle nicht? Verwenden Sie .npmrc

Wenn pnpm die Pakete aus dem Repository abruft, können wir im Stammverzeichnis des Projekts eine .npmrc Datei erstellen.


.npmrc

 link-workspace-packages= true prefer-workspace-packages=true


  • Dadurch wird pnpm angewiesen, zuerst die Arbeitsbereichspakete zu verwenden.
  • Vielen Dank an ZoWnx von Reddit, der mir beim Erstellen einer .nprmc-Datei geholfen hat

20. Konfigurieren Sie die freigegebene tsconfig.json in Libs/Config

Mithilfe der Leistungsfähigkeit von pnpm-Arbeitsbereichen können Sie Konfigurationsdateien erstellen, die projektübergreifend genutzt werden können.


Wir erstellen eine Basis-tsconfig.lib.json, die wir für unsere Bibliotheken verwenden.


Instanziieren Sie innerhalb libs/config eine tsconfig.lib.json :

 touch "libs/config/tsconfig.base.lib.json"


Fügen Sie dann Folgendes hinzu:

tsconfig.base.lib.json

 { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "lib": ["ES2022"], "module": "ESNext", "moduleResolution": "Bundler", "resolveJsonModule": true, "target": "ES2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "allowImportingTsExtensions": true, "allowJs": true, "noUncheckedIndexedAccess": true, "noEmit": true, "incremental": true, "composite": false, "declaration": true, "declarationMap": true, "inlineSources": false, "isolatedModules": true, "noUnusedLocals": false, "noUnusedParameters": false, "preserveWatchOutput": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "sourceMap": true, } } 


21. Fügen Sie die Datenbankverbindung zum Datenbank-Repository hinzu.

 // libs/db/drizzle.config.ts (Yes, this one is at root of the db package, outside the src folder) // We don't want to export this file as this is ran at setup. import "dotenv/config"; // make sure to install dotenv package import { defineConfig } from "drizzle-kit"; export default defineConfig({ dialect: "postgresql", out: "./src/generated", schema: "./src/drizzle/schema.ts", dbCredentials: { url: process.env.DATABASE_URL!, }, // Print all statements verbose: true, // Always ask for confirmation strict: true, });


Die Schemadatei:

 // libs/db/src/drizzle/schema.ts export const User = pgTable("User", { userId: char("userId", { length: 26 }).primaryKey().notNull(), subId: char("subId", { length: 36 }).notNull(), // We are not making this unique to support merging accounts in later // iterations email: text("email"), loginProvider: loginProviderEnum("loginProvider").array().notNull(), createdAt: timestamp("createdAt", { precision: 3, mode: "date" }).notNull(), updatedAt: timestamp("updatedAt", { precision: 3, mode: "date" }).notNull(), });


Die Kundendatei:

 // libs/db/src/drizzle-client.ts import { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import * as schema from "./schema"; export type DrizzleClient = PostgresJsDatabase<typeof schema>; let drizzleClient: DrizzleClient | undefined; type GetClientInput = { databaseUrl: string; env: string; mode?: "cloudflare" | "node"; }; declare var window: typeof globalThis; declare var self: typeof globalThis; export function getDrizzleClient(input: GetClientInput) { const { mode, env } = input; if (mode === "cloudflare") { return generateClient(input); } const globalObject = typeof globalThis !== "undefined" ? globalThis : typeof global !== "undefined" ? global : typeof window !== "undefined" ? window : self; if (env === "production") { drizzleClient = generateClient(input); } else if (globalObject) { if (!(globalObject as any).__db__) { (globalObject as any).__db__ = generateClient(input); } drizzleClient = (globalObject as any).__db__; } else { drizzleClient = generateClient(input); } return drizzleClient; } type GenerateClientInput = { databaseUrl: string; env: string; }; function generateClient(input: GenerateClientInput) { const { databaseUrl, env } = input; const isLoggingEnabled = env === "development"; // prepare: false for serverless try { const client = postgres(databaseUrl, { prepare: false }); const db = drizzle(client, { schema, logger: isLoggingEnabled }); return db; } catch (e) { console.log("ERROR", e); return undefined!; } }


Die Migrationsdatei:

 // libs/db/src/drizzle/migrate.ts import { config } from "dotenv"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; import { drizzle } from "drizzle-orm/postgres-js"; import "dotenv/config"; import path from "path"; config({ path: "../../../../apps/my-remix-cloudflare-app/.dev.vars" }); const ssl = process.env.ENVIRONMENT === "development" ? undefined : "require"; const databaseUrl = drizzle( postgres(`${process.env.DATABASE_URL}`, { ssl, max: 1 }) ); // Somehow the current starting path is /libs/db // Remember to have the DB running before running this script const migration = path.resolve("./src/generated"); const main = async () => { try { await migrate(databaseUrl, { migrationsFolder: migration, }); console.log("Migration complete"); } catch (error) { console.log(error); } process.exit(0); }; main();

Dies sollte nach den Migrationen ausgeführt werden


Und exportieren Sie den Client und das Schema in die Datei src/index.ts . Andere werden zu bestimmten Zeiten ausgeführt.

 // libs/db/src/index.ts export * from "./drizzle/drizzle-client"; export * from "./drizzle/schema "


Fügen Sie in Ihrer package.json das drizzle-kit generate und den Code zum Ausführen des Migrationsbefehls hinzu:

 { "name": "@repo/db", "version": "1.0.0", "main": "./src/index.ts", "module": "./src/index.ts", "types": "./src/index.ts", "scripts": { "db:generate": "drizzle-kit generate", "db:migrate": "dotenv tsx ./drizzle/migrate", }, "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@repo/configs": "workspace:^", "drizzle-kit": "^0.24.1", "drizzle-orm": "^0.33.0", }, "devDependencies": { "@types/node": "^22.5.0" } }

22. Verwenden Sie die freigegebene Datei tsconfig.json für libs/db und libs/utils

Erstellen Sie eine tsconfig.json für libs/db und libs/utils

 touch "libs/db/tsconfig.json" "libs/utils/tsconfig.json"


Fügen Sie dann jeweils hinzu:

 { "extends": "@repo/configs/tsconfig.base.lib.json", "include": ["./src"], }
  • Beachten Sie, dass @repo/configs als Pfad zum Verweisen auf unsere tsconfig.base.lib.json verwendet wird.
  • Es macht unseren Weg sauber.

23. TSX installieren

TypeScript Execute (TSX) ist eine Bibliotheksalternative zu ts-node. Wir werden dies verwenden, um die Migrationen von Drizzle auszuführen.

 pnpm add tsx -D --filter=@repo/db

24. Fügen Sie eine leere .env-Datei im libs/db -Verzeichnis hinzu

 touch "libs/db/.env"


Fügen Sie den folgenden Inhalt hinzu:

 DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" NODE_ENV="development" MODE="node" 


Es sollte ungefähr so aussehen


25. Fügen Sie das libs/db Repository zu unserem Remix-Projekt hinzu

Führen Sie im Stammverzeichnis des Projekts Folgendes aus:

 pnpm add @repo/db --filter=@repo/my-remix-cloudflare-app


Wenn dies nicht funktioniert, gehen Sie zu package.json von apps/my-remix-cloudflare-app und fügen Sie die Abhängigkeit manuell hinzu.

 { "name": "@repo/my-remix-cloudflare-app", "version": "1.0.0", "dependencies": { "@repo/db": "workspace:*" } }

Beachten Sie den workspace:* im Versionsfeld. Dadurch wird pnpm angewiesen, jede Version des Pakets im Arbeitsbereich zu verwenden.


Wenn Sie es über die CLI mithilfe von pnpm add, wird wahrscheinlich etwas wie workspace:^ angezeigt. Solange Sie die lokalen Paketversionen nicht erhöhen, sollte dies keine Rolle spielen.


Wenn Sie dies manuell hinzugefügt haben, führen Sie pnpm install vom Stammverzeichnis des Projekts aus.


Wir sollten in der Lage sein, @repo/db in unserem Projekt zu nutzen.

26. Fügen Sie unseren Dienstprogrammen gemeinsamen Code hinzu:

Fügen Sie diesen Code zur Datei libs/utils/src/index.ts hinzu:

 // libs/utils/src/index.ts export function hellowWorld() { return "Hello World!"; }


27. Installieren Sie die Libs/Utilities in unserer Remix-App:

 pnpm add @repo/db --filter=@repo/my-remix-cloudflare-app

28. (Optional) Starten Sie ein Postgres aus einem Docker-Container

Wenn Sie keine Postgres-Instanz ausführen, können wir eine mit Docker-Compose starten. Beachten Sie, dass ich davon ausgehe, dass Sie Docker kennen.


Erstellen Sie eine Datei docker-compose.yml im Stammverzeichnis des Projekts.

 # Auto-generated docker-compose.yml file. version: '3.8' # Define services. services: postgres: image: postgres:latest restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres ports: - "5432:5432" volumes: - ./postgres-data:/var/lib/postgresql/data pgadmin: # To connect PG Admin, navigate to http://localhost:8500 use: # host.docker.internal # postgres # (username) postgres # (password) postgres image: dpage/pgadmin4 ports: - "8500:80" environment: PGADMIN_DEFAULT_EMAIL: [email protected] PGADMIN_DEFAULT_PASSWORD: admin


Dann können Sie Folgendes ausführen:

 docker-compose up -d

Das Flag -d weist Docker-Compose an, getrennt ausgeführt zu werden, damit Sie wieder Zugriff auf Ihr Terminal haben.

29. Generieren Sie das DB-Schema

Navigieren Sie jetzt zum libs/db-Repository und führen Sie db:generate .

 cd `./libs/db` && pnpm db:generate
  • Beachten Sie, dass db:generate ein Alias für drizzle-kit generate ist.
  • Stellen Sie sicher, dass Sie die richtige .env-Datei haben.
  • Darüber hinaus wird davon ausgegangen, dass Sie eine Postgres-Instanz ausführen.

30. Führen Sie die Migrationen aus.

Wir müssen die Migrationen ausführen, um ein Gerüst für alle Tabellen in unserer Datenbank zu erstellen.


Navigieren Sie zum libs/db-Repository (falls Sie nicht dort sind) und führen Sie db:generate .

 cd `./libs/db` && pnpm db:migrate
  • Beachten Sie, dass db:migrate ein Alias für dotenv tsx ./drizzle/migrate ist.
  • Stellen Sie sicher, dass Sie die richtige .env-Datei haben.
  • Darüber hinaus wird davon ausgegangen, dass Sie eine Postgres-Instanz ausführen.

31. Fügen Sie einen DB-Aufruf in Ihre Remix-App ein.

 // apps/my-remix-cloudflare-app/app/routes/_index.tsx import type { LoaderFunctionArgs } from '@remix-run/cloudflare'; import { json, useLoaderData } from '@remix-run/react'; import { getDrizzleClient } from '@repo/db'; import { Markdown } from '~/components'; import { getFileContentWithCache } from '~/services/github.server'; import { parse } from '~/services/markdoc.server'; export async function loader({ context }: LoaderFunctionArgs) { const client = await getDrizzleClient({ databaseUrl: context.env.DATABASE_URL, env: 'development', mode: 'cloudflare', }); if (client) { const res = await client.query.User.findFirst(); console.log('res', res); } const content = await getFileContentWithCache(context, 'README.md'); return json( { content: parse(content), // user: firstUser, }, { headers: { 'Cache-Control': 'public, max-age=3600', }, }, ); } export default function Index() { const { content } = useLoaderData<typeof loader>(); return <Markdown content={content} />; }


  • Beachten Sie, dass wir hier nicht den Best Practices folgen.
  • Ich rate Ihnen, keine DB-Aufrufe direkt in Ihrem Loader durchzuführen, sondern eine Abstraktion zu erstellen, die diese aufruft.
  • Cloudflare ist anspruchsvoll, wenn es um das Setzen der Umgebungsvariablen geht. Sie werden auf Anfrage übergeben

32. Fügen Sie in Ihren .dev.vars Folgendes hinzu:

apps/mein-Remix-Cloudflare-App/.dev.vars

 DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"

33. Führen Sie das Remix-Projekt aus!

Starten Sie die Postgres-Instanz (falls nicht bereit)

 docker-compose up -d


Starten Sie das Projekt

 pnpm turbo dev 


Erweiterter Anwendungsfall – CQRS in GetLoadContext in Cloudflare Workers.

In meinen Projekten neige ich dazu, ein CQRS-Muster zu implementieren, 2. Dies geht über den Rahmen dieses Tutorials hinaus.


Dennoch neige ich dazu, im Ladekontext einen Mediator (und eine Cookie-Flash-Nachricht) einzufügen, der meine gesamte Remix-Anwendung von meiner Geschäftslogik entkoppelt.


Das sieht ungefähr so aus:

 export const getLoadContext: GetLoadContext = async ({ context, request }) => { const isEnvEmpty = Object.keys(context.cloudflare.env).length === 0; const env = isEnvEmpty ? process.env : context.cloudflare.env; const sessionFlashSecret = env.SESSION_FLASH_SECRET; const flashStorage = createCookieSessionStorage({ cookie: { name: "__flash", httpOnly: true, maxAge: 60, path: "/", sameSite: "lax", secrets: [sessionFlashSecret], secure: true, }, }); return { ...context, cloudflare: { ...context.cloudflare, env, }, dispatch: (await dispatchWithContext({ env: env as unknown as Record<string, string>, request, })) as Dispatch, flashStorage, }; };

Beachten Sie, dass der Dispatch-Code weggelassen wird. Weitere Informationen hierzu finden Sie in meinem Artikel „Wie Sie Ihre TypeScript-Entwicklererfahrung verzehnfachen können“ hier .


Ich kann Remix entfernen oder einen anderen Consumer verwenden, ohne meinen Code zu ändern.


Aber….


Es gibt eine zusätzliche Herausforderung, wenn Sie mit Turborepo in einer Monorepo-Struktur arbeiten.


Wenn Sie eine TypeScript-Datei aus einem Paket innerhalb des Ladekontexts importieren, sagen wir @repo/db gibt Vite einen Fehler zurück, dass die Datei mit der Erweiterung .ts unbekannt ist, und weiß nicht, wie es sie verarbeiten soll.


Dies liegt daran, dass sich der Ladekontext und die Arbeitsbereiche außerhalb des Hauptimportdiagramms der Site befinden und TypeScript-Dateien daher nicht verwendet werden können.


Der Trick besteht darin, tsx zu verwenden und es zu laden, bevor Vite aufgerufen wird. Das funktioniert. Dies ist wichtig, da dadurch die folgenden Einschränkungen überwunden werden:


Cloudflare-Paketabhängigkeiten.


Cloudflare-Paketabhängigkeiten und Vorab-Building

Erstens war dies der Schritt, den ich vermeiden wollte, da er bedeutete, dass ich für jedes der Pakete einen Build-Schritt einführen musste, was mehr Konfiguration bedeutete.


Glücklicherweise funktionierte dies bei Cloudflare Pages nicht. Bestimmte Bibliotheken wie Postgres erkennen die Laufzeit und rufen das erforderliche Paket ab.


Es gibt einen Workaround: Wir können tsx verwenden, um alle TypeScript-Dateien zu laden und sie vor der Ausführung zu transpilieren.


Man kann argumentieren, dass es sich hierbei um einen Schritt vor dem Build handelt, aber da es sich immer noch auf der Repository-Ebene des Remixes befindet, sehe ich bei diesem Ansatz keine größeren Probleme.


Um dies zu lösen, fügen wir tsx als Abhängigkeit hinzu:

 pnpm add tsx -D --filter=@repo/my-remix-cloudflare-app


Und dann müssen wir unser package.json ändern und den tsx-Prozess zu jedem unserer Remix-Skripte hinzufügen:

 { "name": "@repo/my-remix-cloudflare-app", "version": "1.0.0", "scripts": { // Other scripts omitted "build": "NODE_OPTIONS=\"--import tsx/esm\" remix vite:build", "dev": "NODE_OPTIONS=\"--import tsx/esm\" remix vite:dev", "start": "NODE_OPTIONS=\"--import tsx/esm\" wrangler pages dev ./build/client" } }

Extras

Erstellen einer .npmrc Datei

Falls beim Hinzufügen Ihrer lokalen Pakete mit der Befehlszeile Probleme auftreten, können Sie im Stammverzeichnis des Projekts eine .npmrc Datei erstellen.

.npmrc

 link-workspace-packages= true prefer-workspace-packages=true

Dadurch wird pnpm angewiesen, zuerst die Arbeitsbereichspakete zu verwenden.


Danke an ZoWnx von Reddit, der mir beim Erstellen einer .nprmc-Datei geholfen hat

Fallstricke -

  1. Seien Sie vorsichtig bei der Benennung von .client und .server in Ihren Dateien. Auch wenn es sich in einer separaten Bibliothek befindet. Remix verwendet diese, um zu bestimmen, ob es sich um eine Client- oder Serverdatei handelt. Das Projekt wird nicht pro Repository kompiliert, daher wird ein Importfehler ausgegeben!


  2. Wenn Sie Probleme mit plattformübergreifenden Paketen wie Postgres haben, ist die Installation auf Arbeitsbereichsebene besser. Dadurch wird der korrekte Import erkannt. Die direkte Installation im @repo/db-Repository schlägt beim Import in Remix fehl.


Das ist es, Leute!!!

GitHub-Repository

Auf die vollständige Implementierung können Sie hier zugreifen.

Folgen Sie mir in den sozialen Netzwerken!

Ich baue einen öffentlichen automatisierten Testingenieur auf, um diese 1 % Fehler in der Produktion abzufangen.


Ich teile meine Fortschritte bei:


X/Twitter @javiasilis

LinkedIn @javiasilis