Alt taraftaki tuzaklar bölümünü mutlaka okuyun!
Not:
libs
ve packages
dizinlerinin dışında olacaktır.
pnpm dlx create-turbo@latest
Monorepo dizininizde pnpm-workspace.yaml
oluşturun.
İçine şunu ekleyin:
packages: - "apps/*" - "libs/*"
Bu pnpm'e tüm depoların apps
ve libs
içinde yer alacağını söyleyecektir. libs
veya packages
kullanmanın (başka bir yerde görmüş olabileceğiniz gibi) bir önemi olmadığını unutmayın.
pnpm init
{ "name": "@repo/main", "version": "1.0.0", "scripts": {}, "keywords": [], "author": "", "license": "ISC" }
name:@repo/main
Bu bize bunun uygulamanın ana girişi olduğunu söyler. Belirli bir kuralı takip etmeniz veya @
önekini kullanmanız gerekmez. İnsanlar bunu yerel/uzak paketlerden ayırt etmek veya bir organizasyonda gruplandırmayı kolaylaştırmak için kullanırlar.
turbo.json
Dosyasını Oluşturun: { "$schema": "//turbo.build/schema.json", "tasks": { "build": {}, "dev": { "cache": false, "persistent": true }, "start": { "dependsOn": ["^build"], "persistent": true }, "preview": { "cache": false, "persistent": true }, "db:migrate": {} } }
turbo.json dosyası turbo deposuna komutlarımızı nasıl yorumlayacağını söyler. tasks
anahtarının içinde bulunan her şey all package.json'da bulunanlarla eşleşecektir.
Örn: dev
komutu turbo dev
tarafından tetiklenecek ve package.json içinde dev
bulunan tüm paketleri çalıştıracaktır. Eğer bunu turbo'ya dahil etmezseniz, çalıştırılmayacaktır.
apps
klasörü oluşturun mkdir apps
apps
klasöründe bir Remix Uygulaması oluşturun (veya mevcut olanı taşıyın) npx create-remix --template edmundhung/remix-worker-template
Install any dependencies with npm
istediğinde hayır deyin.
name
@repo/my-remix-cloudflare-app
(veya kendi adınız) olarak değiştirin. { - "name": "my-remix-cloudflare-app", + "name": "@repo/my-remix-cloudflare-app", "version": "1.0.0", "keywords": [], "author": "", "license": "ISC" }
apps/<app>/package.json
Root'un package.json
kopyalayın { "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" } }
pnpm add turbo -D -w
-w
bayrağı pnpm'e bunu çalışma alanı köküne kurmasını söyler.
dev
komutunu scripts
ekleyin
packageManager
seçeneğe ekleyin
{ "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 } }
pnpm dev
komutunu çalıştırarak her şeyin çalıştığını doğrulayın pnpm dev
mkdir -p libs/config libs/db libs/utils
src/index.ts
ekleyin. touch libs/config/src/index.ts libs/db/src/index.ts libs/utils/src/index.ts
libs/config/package.json
dosyasına aşağıdakileri ekleyin: { "name": "@repo/config", "version": "1.0.0", "type": "module", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }
libs/db/package.json
için de yapın: { "name": "@repo/db", "version": "1.0.0", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }
libs/utils/package.json
: { "name": "@repo/utils", "version": "1.0.0", "exports": { ".": { "import": "./src/index.ts", "default": "./src/index.ts" } } }
Notlar:
pnpm add drizzle-orm drizle-kit --filter=@repo/db
Postgres'i çalışma alanı düzeyinde yükleyin. Pitfall bölümüne bakın .
pnma add postgres -w
Notlar:
--filter=@repo/db
bayrağı pnpm'e paketi db deposuna eklemesini söyler. pnpm add dotenv -w
Notlar-w
bayrağı pnpm'e bunu root'un package.json dosyasına kurmasını söyler pnpm add @repo/config -r --filter=!@repo/config
Notlar :
-r
bayrağı pnpm'e paketi tüm depolarına eklemesini söyler.--filter=!
bayrağı pnpm'e yapılandırma deposunu hariç tutmasını söyler.!
işaretine dikkat edin Eğer pnpm paketleri depodan çekiyorsa, projenin kökünde bir .npmrc
dosyası oluşturabiliriz.
link-workspace-packages= true prefer-workspace-packages=true
tsconfig.json
Yapılandırın
libs/config
içerisinde tsconfig.lib.json
dosyasını oluşturun:
touch "libs/config/tsconfig.base.lib.json"
{ "$schema": "//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, } }
// 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, });
// 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(), });
// 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!; } }
// 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();
Bu, göçlerden sonra yürütülmelidir
Ve istemciyi ve şemayı src/index.ts
dosyasına aktarın. Diğerleri belirli zamanlarda çalıştırılır.
// libs/db/src/index.ts export * from "./drizzle/drizzle-client"; export * from "./drizzle/schema "
package.json
dosyanıza drizzle-kit generate
komutunu ve geçiş komutunu çalıştıracak kodu ekleyin:
{ "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" } }
libs/db
ve libs/utils
için Paylaşılan tsconfig.json
kullanın libs/db
ve libs/utils
için bir tsconfig.json oluşturun
touch "libs/db/tsconfig.json" "libs/utils/tsconfig.json"
{ "extends": "@repo/configs/tsconfig.base.lib.json", "include": ["./src"], }
@repo/configs
tsconfig.base.lib.json dosyamıza referans vermek için kullanılan yol olduğunu görün. pnpm add tsx -D --filter=@repo/db
libs/db
dizinine boş bir .env ekleyin touch "libs/db/.env"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" NODE_ENV="development" MODE="node"
libs/db
Deposunu Remix Projemize Ekleyin pnpm add @repo/db --filter=@repo/my-remix-cloudflare-app
Eğer bu işe yaramazsa, apps/my-remix-cloudflare-app
dosyasının package.json dosyasına gidin ve bağımlılığı manuel olarak ekleyin.
{ "name": "@repo/my-remix-cloudflare-app", "version": "1.0.0", "dependencies": { "@repo/db": "workspace:*" } }
Sürüm alanında workspace:*
i not edin. Bu, pnpm'e çalışma alanındaki paketin herhangi bir sürümünü kullanmasını söyler.
Eğer pnpm add,
muhtemelen workspace:^
gibi bir şey göreceksiniz. Yerel paket sürümlerini artırmadığınız sürece sorun olmamalı.
Eğer bunu manuel olarak eklediyseniz, projenin kökünden pnpm install
çalıştırın.
Bu kodu libs/utils/src/index.ts
dosyasına ekleyin:
// libs/utils/src/index.ts export function hellowWorld() { return "Hello World!"; }
pnpm add @repo/db --filter=@repo/my-remix-cloudflare-app
Projenin kökünde docker-compose.yml
dosyasını oluşturun.
# 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 //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
docker-compose up -d
-d
bayrağı docker-compose'a bağımsız çalışmasını söyler, böylece terminalinize tekrar erişebilirsiniz.
Şimdi libs/db deposuna gidin ve db:generate
çalıştırın.
cd `./libs/db` && pnpm db:generate
db:generate
drizzle-kit generate
için bir takma ad olduğunu unutmayın
libs/db deposuna gidin (orada değilseniz) ve db:generate
çalıştırın.
cd `./libs/db` && pnpm db:migrate
db:migrate
dotenv tsx ./drizzle/migrate
için bir takma ad olduğunu unutmayın // 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} />; }
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
docker-compose up -d
pnpm turbo dev
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, }; };
Dağıtım kodunun atlandığını unutmayın. TypeScript geliştirme deneyiminizi 10 katına çıkarma hakkında daha fazla bilgiyi buradaki makalemde bulabilirsiniz.
Örneğin, load-context içindeki bir paketten bir TypeScript dosyası içe aktarırsanız, @repo/db
Vite'ın .ts
uzantılı dosyanın bilinmediğine dair bir hata döndüreceğini ve onu nasıl işleyeceğini bilemeyeceğini varsayalım.
İşin püf noktası tsx
kullanmak ve Vite'ı çağırmadan önce yüklemektir, bu işe yarayacaktır. Bu önemlidir çünkü aşağıdaki sınırlamaları aşar:
Cloudflare Paket Bağımlılıkları ve Ön Yapı
Öncelikle kaçınmaya çalıştığım adım buydu, çünkü bu, paketlerin her biri için bir derleme adımı eklemem gerektiği anlamına geliyordu ve bu da daha fazla yapılandırma anlamına geliyordu.
pnpm add tsx -D --filter=@repo/my-remix-cloudflare-app
Daha sonra package.json
dosyamızı düzenleyip, remix scriptlerimizin her birine tsx sürecini eklememiz gerekiyor:
{ "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" } }
.npmrc
Dosyası Oluşturma Yerel paketlerinizi komut satırıyla eklerken sorun yaşıyorsanız, projenin kök dizininde .npmrc
dosyası oluşturabilirsiniz.
link-workspace-packages= true prefer-workspace-packages=true
Bu, pnpm'e öncelikle çalışma alanı paketlerini kullanmasını söyleyecektir.
Dosyalarınızda .client
ve .server
isimlendirmesine dikkat edin. Ayrı bir kütüphanede olsa bile. Remix, bir istemci veya sunucu dosyası olup olmadığını belirlemek için bunları kullanır. Proje depo başına derlenmediğinden içe aktarma hatası verecektir!
İşte bu kadar arkadaşlar!!!