paint-brush
该怎样应用 Vite、Cloudflare、Remix、PNPM 和 Turborepo 創建 Monorepo(无倡导关键步骤) 路过@josejaviasilis
791 讀數
791 讀數

如何使用 Vite、Cloudflare、Remix、PNPM 和 Turborepo 创建 Monorepo(无构建步骤)

经途 Jose Javi Asilis19m2024/08/28
Read on Terminal Reader

太長; 讀書

这将为 Remix 的 Vite Cloduflare 部署创建一个 monorepo,无需构建步骤。这允许您将其扩展到多个目标
featured image - 如何使用 Vite、Cloudflare、Remix、PNPM 和 Turborepo 创建 Monorepo(无构建步骤)
Jose Javi Asilis HackerNoon profile picture
0-item




介绍

我是需要一项以最长的配值将 Remix 与 Vite 和 Cloudflare Workers-Pages 组合适用的方式 。


我见已到另外的存储器库,举列:


但二者总有其他片面性的只性:
  1. 我要想优先建设方案它,担心我要想要用多的手机配置信息残害存储器库。


  2. Cloudflare Workers/Pages 具区别的个人个人目标。动用 tsup 将其身为个人个人目标变好很难搞,而且 Postgres 等系统软件包在引入 Remix 的时候损毁构件依赖症关心。


  3. 我是需要另一种的方式来采用不同于的关键(Remix-Cloudflare、Node/Bun)


既然如此这般,我能是要感谢信顾客,由于顾客为构建此对象抹平了道路施工!


请务必阅读底部的陷阱部分!

在社交媒体上关注我!

我时未政府信息创设一家自动的化软件测试公司,以抓取制造中会出现的那 1% 的内部错误。


我分享一下我的进展情况:


GitHub 存储库

您能够。

一步步

要求

  1. NodeJS
  2. 全国公共管理委员会
  3. Docker(可选 - 用于本地数据库示例)


也许这将引导作用您结束一位新的一个纯粹随意调节库,但将替换成的随意调节库转移为一位一个纯粹随意调节库也是完成可行的。


这也假如您还具有有一些 Mono Repo 技巧。


笔记:

  • “at root” 指的是 monorepository 的起始路径。对于此项目,它将位于libspackages目录之外。

安装 Turborepo

Turborepo 在您的吊模理器操作区表层操作,以治理活动的js和的输送(它甚至于是可以临时文件您的的输送)。到目前就行就行,它是除开 Rush(我都没有来尝试过且不最喜欢)囿于独一无二才可以操作的 mono-repo 机器。


NX 不苹果支持 Remix 的 Vite(截止目前创作本论文时 - 2024 年 8 月 28 日)。


 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": "//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的命令。tasks 键内的所有内容都将与 all package.json 中找到的内容匹配。


请提前准备,你们构成了七个下令。这样的下令与每一位储存方式库的 package.json 的角本环节中的下令相配备。而且,并不意味着一切 package.json 都务必保持这样的下令。


例如: dev命令将由turbo dev触发,它将执行 package.json 中找到的所有dev包。如果你没有在 turbo 中包含它,它将不会执行。

4. 在项目根目录中创建一个apps文件夹

mkdir apps

5. 在apps文件夹中创建 Remix 应用程序(或移动现有应用程序)

 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. 将依赖项和 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. 将以下条目添加到根 package.json

  • dev命令添加到scripts

  • 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”字段。这会告诉其他存储库从哪里导入包。
  • 我们指定“名称”字段。这用于安装包并将其引用到其他存储库。

16.(数据库)- 添加 Drizzle 和 Postgres

笔记:

  • 我开端鄙夷 ORM。我花了 10 数年周期了解了 6 种各不相同的 ORM,特别某些理论知识是不可介绍的。
  • 当新的技术存在时,你还碰见话题。Prisma 不能够软件拆箱即用的 Cloudflare Worker。
  • 得到了 LLM,撰写麻烦的 SQL 查寻比去年十分简易。
  • 借鉴 SQL 是一种种专用语言的,还有不太将改变了。


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


在工作区级别安装 Postgres。请参阅陷阱部分

 pnma add postgres -w


笔记:

  • --filter=@repo/db标志告诉 pnpm 将包添加到 db 存储库。

17.将 dotenv 添加到工作区存储库

pnpm add dotenv -w
文章
  • -w标志告诉 pnpm 将其安装在根目录的 package.json 中

18.将配置项目添加到所有项目中。

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

注意

  • -r标志告诉 pnpm 将包添加到所有存储库。
  • --filter=!标志告诉 pnpm 排除配置存储库。
  • 注意包名称前的!

19.(要选)里面的ps命令没有操作?操作 .npmrc

如果 pnpm 正在从存储库中提取包,我们可以在项目的根目录创建一个.npmrc文件。


.npmrc
 link-workspace-packages= true prefer-workspace-packages=true


  • 这将告诉 pnpm 首先使用工作区包。
  • 感谢 Reddit 的帮助我制作了 .nprmc 文件

20. 在 Libs/Config 中配置共享的tsconfig.json

利于 pnpm 工做区的专业作用,您可加入可跨建设项目分享的硬件配置文件下载。


你们大家将开启1个应用于你们大家的库的总体 tsconfig.lib.json。


libs/config中实例化一个tsconfig.lib.json

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


并且含有左右信息内容: 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, } } 


21.将数据库连接添加到数据库存储库。

 // 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.json中,添加drizzle-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. 使用共享的tsconfig.json作为libs/dblibs/utils

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存储库添加到我们的 Remix 项目

从的项目的根导航启用:
 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.向我们的实用程序添加一些共享代码:

将此代码添加到libs/utils/src/index.ts文件中:

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


27. 将 Libs/Utils 安装到我们的 Remix 应用程序中:

 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 //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. 生成数据库模式

现在,导航到 libs/db 存储库并运行db:generate

 cd `./libs/db` && pnpm db:generate
  • 请注意, db:generatedrizzle-kit generate的别名
  • 验证您是否具有正确的 .env。
  • 此外,这假设您正在运行一个 Postgres 实例。

30. 运行迁移。

企业可以运营转迁来搭配数据源库文件的那些表。


导航到 libs/db 存储库(如果您不在那里)并运行db:generate

 cd `./libs/db` && pnpm db:migrate
  • 请注意, db:migrate是以下命令的别名: dotenv tsx ./drizzle/migrate
  • 验证您是否具有正确的 .env。
  • 此外,这假设您正在运行一个 Postgres 实例。

31. 在您的 Remix 应用程序中插入 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. 执行Remix项目!

启动时 postgres 范例(假设已经準備好)
 docker-compose up -d


开机启动该项目
pnpm turbo dev 


高级用例 - Cloudflare Workers 中 GetLoadContext 中的 CQRS。

在我的该项目中,我倾向性于进行 。这少于了本步骤的依据。


一直以来这么,在打开前后一文,我局限性于注射到一中介人(和一 cookie 闪存传闻),将我的整 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 或便用相关网上消费群体,而不用改进我的编码。


但…。


当您实用 turborepo 在 monorepo 的结构中岗位时,还是受到木制托盘的挑戰。


如果你从加载上下文中的包中导入 TypeScript 文件,比如说@repo/db Vite 将返回一个错误,提示“扩展名为.ts文件未知,并且不知道如何处理它”。


遭受这样的实际情况的根本原因是,load-context + workingspaces 设在网站的首要把手机通讯录图模版,最终得以引发 TypeScript 信息是没办法发挥出来用途。


诀窍是在调用 Vite之前使用tsx并加载它,这样就可以了。这很重要,因为它克服了以下限制:


Cloudflare 包依赖症项。


Cloudflare 软件包依赖项和预构建

应先,这就是你会议室应对的方法,会因为这象征着我需为每包引进这个勾勒方法,这象征着大多的安装。


运气的是,这对 Cloudflare Pages 没法做用。某一库(列举 Postgres)将监测运作时并添加必备的包。


有一家个解决办法具体方法:企业能够 食用 tsx 载入全部 TypeScript 相关文件并在程序执行的时候对其开展变为。


您能能争辩说这个是一种预构造 步奏,但伴随它仍处在 remix 的随意调节库职别,但是我认同各种方式方法会发生比较重要毛病。


为了能够解决方法这样的困难,各位修改 tsx 作依赖症项:
 pnpm add tsx -D --filter=@repo/my-remix-cloudflare-app


然后,我们需要修改package.json并将 tsx 进程添加到我们的每个 remix 脚本中:

 { "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 前提是食用运作区包。


感谢领导 Reddit 的益处我制作而成了 .nprmc 信息

陷阱 -

  1. 小心命名文件中的.client.server 。即使它位于单独的库中。Remix 使用这些来确定它是客户端文件还是服务器文件。该项目不是按存储库编译的,因此它会抛出导入错误!


  2. 若是 您在适用 Postgres 等多公司app软件包时找到状况,建议在办公区类别按装它。它将查重正确性的添加。会直接在 @repo/db 保存库文件按装它会在将其添加 Remix 时超时。


就是这样了,伙计们!!!

GitHub 存储库

您都可以。

在社交媒体上关注我!

我稍后对外公布建设是一个自主化测试仪过程师来捕获生产制造中发生的那 1% 的失败。


我转发我的发展:


바카라사이트 바카라사이트 온라인바카라