paint-brush
Vite, Cloudflare, Remix, PNPM 및 Turborepo를 사용하여 모노레포를 만드는 방법(빌드 단계 없음)~에 의해@josejaviasilis
1,087 판독값
1,087 판독값

Vite, Cloudflare, Remix, PNPM 및 Turborepo를 사용하여 모노레포를 만드는 방법(빌드 단계 없음)

~에 의해 Jose Javi Asilis19m2024/08/28
Read on Terminal Reader

너무 오래; 읽다

이렇게 하면 빌드 단계 없이 Remix의 Vite Cloduflare 배포를 위한 모노레포가 생성됩니다. 이를 통해 여러 대상으로 확장할 수 있습니다.
featured image - Vite, Cloudflare, Remix, PNPM 및 Turborepo를 사용하여 모노레포를 만드는 방법(빌드 단계 없음)
Jose Javi Asilis HackerNoon profile picture
0-item




소개

최소한의 구성으로 Vite와 Cloudflare Workers-Pages와 함께 Remix를 사용할 수 있는 방법이 필요했습니다.


나는 다음과 같은 다른 저장소를 보았습니다.


하지만 그들에게는 몇 가지 한계가 있었습니다.

  1. 더 많은 설정 파일로 저장소를 오염시키고 싶지 않았기 때문에 사전 빌드를 원하지 않았습니다.


  2. Cloudflare Workers/Pages에는 다른 대상이 있습니다. Postgres와 같은 패키지가 Remix로 가져올 때 노드 종속성이 깨지는 것을 잡아당기기 때문에 tsup으로 대상을 지정하는 것이 까다로워졌습니다.


  3. 또한 다양한 대상(Remix-Cloudflare, Node/Bun)을 사용할 수 있는 방법도 필요했습니다.


그럼에도 불구하고, 이것이 가능하게 된 길을 열어준 그들에게 감사드립니다!


꼭 마지막 부분의 함정 섹션을 읽어보세요!

소셜 미디어에서 저를 팔로우하세요!

저는 운영 환경에서 1%의 오류를 포착하기 위해 자동화된 테스트 플랫폼을 공개적으로 구축하고 있습니다.


나는 다음에 대한 진행 상황을 공유합니다:


X/트위터 @javiasilis

링크드인 @javiasilis

GitHub 저장소

전체 구현 내용은 여기에서 확인할 수 있습니다.

단계별

요구 사항

  1. 노드JS
  2. PNPM
  3. Docker(선택 사항 - 로컬 데이터베이스 예)


이 과정에서는 새로운 모노 저장소를 소개하지만, 기존 모노 저장소를 모노 저장소로 변환하는 것도 아무런 문제가 없습니다.


또한 이를 위해서는 모노 리포에 대한 지식이 있어야 합니다.


메모:

  • "at root"는 모노리포지토리의 시작 경로를 말합니다. 이 프로젝트의 경우 libspackages 디렉터리 외부에 있을 것입니다.

Turborepo 설치

Turborepo는 패키지 관리자의 작업 공간 위에서 작동하여 프로젝트의 스크립트와 출력을 관리합니다(출력을 캐시할 수도 있음). 지금까지 Rush(시도해보지 않았고 마음에 들지 않음) 외에 작동할 수 있는 유일한 모노리포 도구입니다.


NX에서는 Remix의 Vite를 지원하지 않습니다(이 글을 쓰는 시점인 2024년 8월 28일 기준).


https://turbo.build/

 pnpm dlx create-turbo@latest

1. PNPM 작업 공간 구성

PNPM의 작업 공간 기능을 사용하여 종속성을 관리합니다.


Monorepo 디렉토리에서 pnpm-workspace.yaml 만듭니다.


그 안에 다음을 추가하세요.

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


이렇게 하면 pnpm에 모든 저장소가 appslibs 내부에 있을 것이라고 알려줍니다. libspackages 사용하는 것은 중요하지 않습니다(다른 곳에서 보셨을 수도 있지만).

2. 프로젝트 루트에 빈 package.json을 만듭니다.

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


name:@repo/main 이것은 이것이 애플리케이션의 주요 항목임을 알려줍니다. 특정 규칙을 따르거나 @ 접두사를 사용할 필요가 없습니다. 사람들은 로컬/원격 패키지와 구별하거나 조직으로 그룹화하기 쉽게 하기 위해 이를 사용합니다.

3. 프로젝트 루트에 turbo.json 파일을 만듭니다.

 { "$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": {} } }


turbo.json 파일은 turbo repo에 명령을 해석하는 방법을 알려줍니다. tasks 키 안에 있는 모든 것은 all package.json에서 발견된 것과 일치합니다.


네 개의 명령을 정의한다는 점에 유의하세요. 이는 각 저장소의 package.json의 스크립트 섹션에 있는 명령과 일치합니다. 그러나 모든 package.json이 이러한 명령을 구현해야 하는 것은 아닙니다.


예: dev 명령은 turbo dev 에 의해 트리거되고 package.json에서 dev 가 발견된 모든 패키지를 실행합니다. turbo에 포함하지 않으면 실행되지 않습니다.

4. 프로젝트 루트에 apps 폴더를 만듭니다.

 mkdir apps

5. apps 폴더에서 리믹스 앱 만들기(또는 기존 앱 이동)

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


Install any dependencies with npm 하라고 하면 '아니요'를 선택하세요.


  • 이렇게 보여야 합니다

6. package.json의 name @repo/my-remix-cloudflare-app (또는 사용자 이름)으로 변경합니다.

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

7. Dependencies 및 devDependencies를 apps/<app>/package.json 에서 Root의 package.json 으로 복사합니다.

예를 들어:

<루트>/package.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. 루트 수준에서 Turbo를 devDependency로 추가합니다(이렇게 하면 Remix의 모든 패키지가 설치됩니다).

turbo가 package.json의 devDependencies 안에 있는지 확인하세요. 나열되어 있지 않으면 다음 명령을 실행하세요.

 pnpm add turbo -D -w


-w 플래그는 pnpm에게 작업 공간 루트에 설치하라고 알려줍니다.

9. Root package.json에 다음 항목을 추가합니다.

  • scriptsdev 명령 추가

  • 옵션에 packageManager 추가합니다


 { "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. pnpm dev 실행하여 모든 것이 제대로 작동하는지 확인하세요.

 pnpm dev

11. 프로젝트 루트에 Libs 폴더를 만듭니다. config, db, utils를 추가합니다.

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

12. 각 패키지에 대한 src/index.ts 추가합니다.

 touch libs/config/src/index.ts libs/db/src/index.ts libs/utils/src/index.ts
  • index.ts 파일은 모든 패키지를 내보내는 데 사용됩니다.
  • 우리는 폴더를 진입점으로 삼아 모든 것을 압축적으로 만들 것입니다.
  • 이것은 관례이므로 따르지 않아도 됩니다.

13. 빈 package.json을 만들고 libs/config/package.json 파일에 다음을 추가합니다.

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

14. libs/db/package.json 에 대해서도 같은 작업을 수행합니다.

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

15. 그리고 libs/utils/package.json :

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


  • 우리는 "exports" 필드를 지정합니다. 이것은 다른 저장소에 패키지를 어디에서 가져올지 알려줍니다.
  • "name" 필드를 지정합니다. 이는 패키지를 설치하고 다른 저장소를 참조하는 데 사용됩니다.

16. (DB) - Drizzle과 Postgres 추가

참고사항:

  • 저는 ORM을 싫어하기 시작했습니다. 저는 6개의 다른 ORM을 배우는 데 10년 이상을 보냈고, 그것은 당신이 전달할 수 없는 지식입니다.

  • 새로운 기술이 나오면 문제가 발생합니다. Prisma는 Cloudflare Workers를 기본적으로 지원하지 않습니다.

  • LLM을 이용하면 복잡한 SQL 쿼리를 그 어느 때보다 쉽게 작성할 수 있습니다.

  • SQL 학습은 보편적인 언어이기 때문에 앞으로는 변하지 않을 것 같습니다.


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


작업공간 레벨에서 Postgres를 설치합니다. 함정 섹션을 참조하세요 .

 pnma add postgres -w


참고사항:

  • --filter=@repo/db 플래그는 pnpm에게 패키지를 db 저장소에 추가하라고 알려줍니다.

17. Workspace Repository에 dotenv를 추가합니다.

 pnpm add dotenv -w

노트

  • -w 플래그는 pnpm에게 루트의 package.json에 설치하라고 지시합니다.

18. 모든 프로젝트에 Config 프로젝트를 추가합니다.

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

참고사항 :

  • -r 플래그는 pnpm에게 모든 저장소에 패키지를 추가하라고 알려줍니다.
  • --filter=! 플래그는 pnpm에게 config 저장소를 제외하라고 지시합니다.
  • 패키지 이름 앞에 ! 를 주의하세요.

19. (선택 사항) 위의 명령이 작동하지 않습니까? .npmrc를 사용하십시오.

pnpm이 저장소에서 패키지를 끌어오는 경우 프로젝트 루트에 .npmrc 파일을 만들 수 있습니다.


.npmrc

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


  • 이렇게 하면 pnpm이 작업 공간 패키지를 먼저 사용하게 됩니다.
  • .nprmc 파일을 만드는 데 도움을 준 Reddit의 ZoWnx 에게 감사드립니다.

20. Libs/Config 내부에서 공유 tsconfig.json 구성

pnpm 작업 공간의 힘을 활용하면 여러 프로젝트에서 공유할 수 있는 구성 파일을 만들 수 있습니다.


라이브러리에 사용할 기본 tsconfig.lib.json을 생성하겠습니다.


libs/config 내부에서 tsconfig.lib.json 을 인스턴스화합니다.

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


그리고 다음을 추가합니다.

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. db 저장소에 db 연결을 추가합니다.

 // 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();

이것은 마이그레이션 후에 실행되어야 합니다.


그리고 src/index.ts 파일에 클라이언트와 스키마를 내보냅니다. 다른 것들은 특정 시간에 실행됩니다.

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


package.jsondrizzle-kit generate 와 마이그레이션 명령을 실행하는 코드를 추가합니다.

 { "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. libs/dblibs/utils 에 대해 공유 tsconfig.json 사용합니다.

libs/dblibs/utils 에 대한 tsconfig.json을 만듭니다.

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


그런 다음 각각에 다음을 추가합니다.

 { "extends": "@repo/configs/tsconfig.base.lib.json", "include": ["./src"], }
  • @repo/configs tsconfig.base.lib.json을 참조하는 경로로 사용되는지 확인합니다.
  • 우리의 길을 깨끗하게 만들어줍니다.

23. TSX 설치

TypeScript Execute(TSX)는 ts-node의 라이브러리 대안입니다. 이것을 사용하여 drizzle의 마이그레이션을 실행합니다.

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

24. libs/db 디렉토리에 빈 .env 추가

 touch "libs/db/.env"


다음 내용을 추가합니다.

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


다음과 같이 보여야 합니다.


25. 리믹스 프로젝트에 libs/db 저장소 추가

프로젝트 루트에서 다음을 실행합니다.

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


여기서도 작동하지 않는다면 apps/my-remix-cloudflare-app 의 package.json으로 가서 종속성을 수동으로 추가하세요.

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

버전 필드의 workspace:* 에 주의하세요. 이것은 pnpm이 작업공간에서 패키지의 모든 버전을 사용하도록 지시합니다.


pnpm add, 아마도 workspace:^ 와 비슷한 것을 볼 수 있을 것입니다. 로컬 패키지 버전을 늘리지 않는 한 문제가 되지 않습니다.


수동으로 추가한 경우 프로젝트 루트에서 pnpm install 실행하세요.


우리 프로젝트에서 @repo/db를 사용할 수 있어야 합니다.

26. 우리의 Utils에 공유 코드 추가:

libs/utils/src/index.ts 파일에 이 코드를 추가하세요:

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


27. Remix 앱에 Libs/Utils 설치:

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

28. (선택 사항) Docker 컨테이너에서 Postgres 실행

Postgres 인스턴스를 실행하지 않는 경우 docker-compose를 사용하여 인스턴스를 시작할 수 있습니다. 참고로, Docker를 알고 있다고 가정합니다.


프로젝트 루트에 docker-compose.yml 파일을 만듭니다.

 # 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


그런 다음 다음을 실행할 수 있습니다.

 docker-compose up -d

-d 플래그는 docker-compose가 분리된 상태로 실행되도록 하여 터미널에 다시 액세스할 수 있도록 합니다.

29. DB 스키마 생성

이제 libs/db 저장소로 이동하여 db:generate 실행하세요.

 cd `./libs/db` && pnpm db:generate
  • db:generate drizzle-kit generate 의 별칭입니다.
  • .env가 올바른지 확인하세요.
  • 또한, 이는 Postgres 인스턴스가 실행 중이라고 가정합니다.

30. 마이그레이션을 실행합니다.

데이터베이스 내의 모든 테이블을 스캐폴딩하기 위해 마이그레이션을 실행해야 합니다.


libs/db 저장소로 이동합니다(없는 경우). 그리고 db:generate 실행합니다.

 cd `./libs/db` && pnpm db:migrate
  • db:migrate dotenv tsx ./drizzle/migrate 의 별칭입니다.
  • .env가 올바른지 확인하세요.
  • 또한, 이는 Postgres 인스턴스가 실행 중이라고 가정합니다.

31. 리믹스 앱에 DB 호출을 삽입합니다.

 // 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} />; }


  • 여기서는 모범 사례를 따르지 않는다는 점에 유의하세요.
  • 로더 내에서 직접 DB 호출을 하지 말고, 이를 호출하는 추상화를 만드는 것이 좋습니다.
  • Cloudflare는 환경 변수를 설정하는 데 있어 까다롭습니다. 요청으로 전달됩니다.

32. .dev.vars에 다음을 추가합니다.

앱/my-remix-cloudflare-app/.dev.vars

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

33. 리믹스 프로젝트를 실행하세요!

postgres 인스턴스를 시작합니다(준비되지 않은 경우)

 docker-compose up -d


프로젝트 시작

 pnpm turbo dev 


고급 사용 사례 - Cloudflare Workers의 GetLoadContext에서 CQRS 사용.

내 프로젝트에서 나는 CQRS 패턴을 구현하는 경향이 있습니다. 2. 이것은 이 튜토리얼의 범위를 벗어납니다.


그럼에도 불구하고, 로드 컨텍스트 내에서 전체 Remix 애플리케이션을 비즈니스 로직에서 분리하는 중재자(및 쿠키 플래시 메시지)를 삽입하는 경향이 있습니다.


이는 다음과 같습니다.

 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, }; };

디스패치 코드는 생략된 점에 유의하세요. 자세한 내용은 TypeScript 개발 경험을 10배 높이는 방법에 대한 제 기사 에서 확인할 수 있습니다.


코드를 변경하지 않고도 Remix를 제거하거나 다른 소비자를 사용할 수 있습니다.


하지만….


터보레포를 사용하여 모노레포 구조에서 작업하는 경우에는 추가적인 어려움이 있습니다.


로드 컨텍스트 내에서 패키지에서 TypeScript 파일을 가져오는 경우, 예를 들어 @repo/db Vite는 확장자가 .ts 인 파일을 알 수 없다는 오류를 반환하고, 해당 파일을 어떻게 처리해야 할지 알 수 없습니다.


이런 일이 일어나는 이유는 로드 컨텍스트 + 작업 공간이 사이트의 기본 가져오기 그래프 밖에 있어서 TypeScript 파일이 재생 범위 밖에 있기 때문입니다.


비결은 tsx 사용하여 Vite를 호출하기 전에 로드하는 것입니다. 이렇게 하면 작동합니다. 이는 다음과 같은 제한을 극복하기 때문에 중요합니다.


Cloudflare 패키지 종속성.


Cloudflare 패키지 종속성 및 사전 빌드

우선, 이 단계는 제가 피하려고 했던 단계였습니다. 이 단계를 수행하려면 각 패키지에 대한 빌드 단계를 도입해야 하며, 이는 더 많은 구성을 필요로 하기 때문입니다.


다행히도, 이것은 Cloudflare Pages에서는 작동하지 않았습니다. Postgres와 같은 특정 라이브러리는 런타임을 감지하고 필요한 패키지를 끌어옵니다.


해결 방법이 있습니다. tsx를 사용하여 모든 TypeScript 파일을 로드하고 실행하기 전에 이를 트랜스파일할 수 있습니다.


이것이 사전 빌드 단계라고 주장할 수도 있지만, 여전히 리믹스의 저장소 수준이기 때문에 이 접근 방식에는 큰 문제가 없다고 봅니다.


이를 해결하려면 tsx를 종속성으로 추가합니다.

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


그런 다음 package.json 수정하고 각 리믹스 스크립트에 tsx 프로세스를 추가해야 합니다.

 { "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 파일 생성

명령줄로 로컬 패키지를 추가하는 동안 문제가 발생하는 경우 프로젝트 루트에 .npmrc 파일을 만들 수 있습니다.

.npmrc

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

이렇게 하면 pnpm이 작업 공간 패키지를 먼저 사용하게 됩니다.


.nprmc 파일을 만드는 데 도움을 준 Reddit의 ZoWnx 에게 감사드립니다.

함정 -

  1. 파일에서 .client.server 이름을 지정할 때는 조심하세요. 별도의 라이브러리에 있더라도요. Remix는 이를 사용하여 클라이언트 파일인지 서버 파일인지 판별합니다. 프로젝트는 리포지토리별로 컴파일되지 않으므로 가져오기 오류가 발생합니다!


  2. Postgres와 같은 다중 플랫폼 패키지에 문제가 있는 경우 작업 공간 수준에서 설치하는 것이 좋습니다. 적절한 가져오기를 감지합니다. @repo/db 저장소에 직접 설치하면 Remix로 가져올 때 중단됩니다.


그게 다예요, 여러분!!!

GitHub 저장소

전체 구현 내용은 여기에서 확인할 수 있습니다.

소셜에서 저를 팔로우하세요!

저는 운영 환경에서 1%의 오류를 포착하기 위해 자동화 테스트 엔지니어를 공개적으로 양성하고 있습니다.


나는 다음에 대한 진행 상황을 공유합니다:


X/트위터 @javiasilis

링크드인 @javiasilis