首頁  >  文章  >  web前端  >  從零到發布:我們 Remix 驅動的開發之旅的主要收穫

從零到發布:我們 Remix 驅動的開發之旅的主要收穫

王林
王林原創
2024-09-07 00:01:08430瀏覽

大約六個​​月前,我做了一些人所說的大膽決定,選擇 Remix 作為我們公司 Web 應用程式的基礎。快進到今天,我認為是時候退一步反思我們所做的選擇了。我將回顧所做的主要基礎設施決策,並附帶一些實際使用範例。

所以,事不宜遲,讓我們直接進入這段旅程的亮點和低谷——滿足感和經驗教訓的結合。

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Remix(或者我應該說 Rea​​ct Router?)

亮點:混音

這可能是我當時做出的「風險最大」的基礎設施決策,因為 Remix 遠沒有 NextJS 那麼受歡迎,而且據我所知,大企業使用 Remix 的例子並不多。
快進到今天 - ChatGPT 幾天前從 Next 遷移到 Remix!

正如我在上一篇文章中詳細介紹的那樣,我選擇Remix 的原因有很多,其中一些原因是它的簡單性、「全端」方面(即,利用remix 伺服器作為「前端的後端」)以及它對路由、數據獲取和突變。

幸運的是,Remix 交付了?該框架直觀、易於學習和教導他人,並確保使用最佳實踐,使編寫程式碼和測試變得簡單。

與 Remix 合作幾個月後,他們宣布與 React Router 正式合併,我希望這能說服更多的人使用它,就像他們轉向 vite 所做的那樣。

在很多情況下我都清楚地意識到 Remix 是正確的選擇。我將給出一個我最近處理的實際範例 - 在混音伺服器中使用單一記錄器實例能夠記錄和追蹤整個應用程式中的操作和錯誤,以增強我們的監控能力。實作非常簡單:

第 1 步 - 建立記錄器(在我的例子中,我使用了 Winston,它與我們用於監控的 Datadog 配合得很好)

第 2 步 - 將記錄器新增至伺服器的載入上下文(在我的例子中是express):

app.all(
  '*',
  createRequestHandler({
    getLoadContext: () => ({
      logger,
      // add any other context variables here
    }),
    mode: MODE,
    // ...
  }),
);

第 3 步(適用於 Typescript 使用者)- 更新 Remix 的預設類型定義以將記錄器包含在應用程式載入上下文中

import '@remix-run/node';
import { type Logger } from 'winston';

declare module '@remix-run/node' {
  interface AppLoadContext {
    logger: Logger;
  }
}

第 4 步 - 在任何路線的加載器或操作中隨意使用記錄器!

export async function action({ request, context }: ActionFunctionArgs) {

  try {
    await someAction();
  } catch (e) {
    context.logger.error(e);
  }
}

在我們結束本節之前,我想說的是,還有一些我希望Remix 擁有但它們還沒有的東西,比如用於流數據/組件的RSC 實現,以及非常適合身份驗證的路由中間件/授權。幸運的是,看起來這些東西(以及其他很酷的功能)在他們的路線圖中被優先考慮,所以希望我們能盡快得到它們!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tanstack 查詢。一直以來的最愛

亮點:反應查詢

根據我過去的正面經驗,選擇 @tanstack/react-query 對我來說是一個簡單的決定,這次也沒有讓人失望。該 API 是通用的、可擴展的,並且以最佳方式不拘一格 — 使其易於與其他工具整合。

我非常喜歡它,所以我選擇了它,因為我知道我們的內部 API 是基於 GraphQL 的,而不是更明顯的選擇 Apollo Client。原因有很多:Tanstack Query 擁有出色的 API,它比 Apollo 輕得多,並且因為我不想依賴像 GraphQL 這樣專門針對特定技術量身定制的工具,以防萬一我們需要切換或合併其他技術。

此外,由於我們使用 Remix,我可以充分利用 Tanstack Query 的 SSR 功能 - 在伺服器端預取查詢,同時仍保持在客戶端變異、無效或重新取得這些查詢的能力。這是一個簡化的範例:

import { dehydrate, QueryClient, HydrationBoundary, useQuery } from '@tanstack/react-query';
import { json, useLoaderData } from '@remix-run/react';


const someDataQuery = {
  queryKey: ['some-data'],
  queryFn: () => fetchSomeData()
}

export async function loader() {
  const queryClient = new QueryClient();
  try {
    await queryClient.fetchQuery(someDataQuery);

    return json({ dehydrate: dehydrate(queryClient) });
  } catch (e) {
    // decide whether to handle the error or continue to
    // render the page and retry the query in the client
  }
}

export default function MyRouteComponent() {
  const { dehydratedState } = useLoaderData<typeof loader>();
  const { data } = useQuery(someDataQuery);

  return (
           <HydrationBoundary state={dehydratedState}>
             <SomeComponent data={data} />
           </HydrationBoundary />
  );
}

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tailwind CSS

Highlight: Tailwind

I was initially skeptical about Tailwind, having never used it before, and because I didn’t quite understand the hype (it seemed to me at first just like syntactic sugar over CSS). However, I decided to give it a try because of its strong recommendations and popularity within the community, and I’m really glad I did. Tailwind’s utility-first approach made it incredibly easy to build a consistent and robust design system right from the start, which, looking back, was a total game changer.
It also pairs perfectly with shadcn, which we used, and together they allowed me to deliver quickly while keeping everything modular and easy to modify later on - a crucial advantage in a startup environment.

I also really like how easy it is to customize tailwind's theme to your needs - for example, overriding tailwind's default scheme:

First, define your colors as variable's under tailwind's main .css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {

  :root {
    /* define the primitive design system tokens */
    --colors-blue-100: hsl(188 76% 90%);
    --colors-blue-200: hsl(187 63% 82%);
    --colors-blue-25: hsl(185 100% 98%);
    --colors-blue-300: hsl(190 52% 74%);
    --colors-blue-400: hsl(190 52% 61%);
    --colors-blue-50: hsl(188 92% 95%);
    --colors-blue-500: hsl(190 74% 39%);
    --colors-blue-600: hsl(191 77% 34%);
    --colors-blue-700: hsl(190 51% 35%);
    --colors-blue-800: hsl(191 52% 29%);
    --colors-blue-900: hsl(190 51% 23%);
    --colors-blue-950: hsl(190 52% 17%);
    --colors-gray-100: hsl(0 0 90%);
    --colors-gray-200: hsl(0 0 85%);
    --colors-gray-25: hsl(0 0 98%);
    --colors-gray-300: hsl(0 0 73%);
    --colors-gray-400: hsl(0 1% 62%);
    --colors-gray-50: hsl(0 0 94%);
    --colors-gray-500: hsl(0 0% 53%);
    --colors-gray-600: hsl(0 0 44%);
    --colors-gray-700: hsl(0 0 36%);
    --colors-gray-800: hsl(0 2% 28%);
    --colors-gray-900: hsl(0 0 20%);
    --colors-gray-950: hsl(0 0 5%);
    --colors-red-100: hsl(4 93% 94%);
    --colors-red-200: hsl(3 96% 89%);
    --colors-red-25: hsl(12 100% 99%);
    --colors-red-300: hsl(4 96% 80%);
    --colors-red-400: hsl(4 92% 69%);
    --colors-red-50: hsl(5 86% 97%);
    --colors-red-500: hsl(4 88% 61%);
    --colors-red-600: hsl(4 74% 49%);
    --colors-red-700: hsl(4 76% 40%);
    --colors-red-800: hsl(4 72% 33%);
    --colors-red-900: hsl(8 65% 29%);
    --colors-red-950: hsl(8 75% 19%);

    /*
      ...
    */

    /* define the semantic design system tokens */

    --primary-light: var(--colors-blue-200);
    --primary: var(--colors-blue-600);
    --primary-dark: var(--colors-blue-800);
    --primary-hover: var(--colors-blue-50);

    --text-default-primary: var(--colors-gray-700);
    --text-default-secondary: var(--colors-gray-800);
    --text-default-tertiary: var(--colors-gray-900);
    --text-default-disabled: var(--colors-gray-300);
    --text-default-read-only: var(--colors-gray-400);

    --disabled: var(--colors-gray-300);
    --tertiary: var(--colors-gray-50);

    /*
      ...
    */
  }
}

Then, extend Tailwind's default theme via the tailwind config file:

import { type Config } from 'tailwindcss';

const ColorTokens = {
  BLUE: 'blue',
  GRAY: 'gray',
  RED: 'red',
} as const;

const generateColorScale = (colorName: string) => {
  const scales = [25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
  return scales.reduce(
    (acc, scale) => {
      acc[scale] = `var(--colors-${colorName}-${scale})`;
      return acc;
    },
    {} as Record<string, string>,
  );
};

export const customColors = Object.values(ColorTokens).reduce((acc, color) => {
  return {
    ...acc,
    [color]: generateColorScale(color),
  };
}, {});

const config = {
  // ... additional config
  theme: {
    extend: {
      colors: customColors
    },
  },
} satisfies Config;

export default config;

This is just the tip of the iceberg - you can go on to define custom spacing, text sizing and much more!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Playwright - makes writing e2e tests fun

Highlight: Playwright

Previously using Cypress, I was inclined to choose it, but I kept hearing hype around Playwright and figured I'll research it extensively before making a decision. After comparing Playwright with Cypress, it was clear Playwright is the right choice to make - the fact it comes with parallel execution out of the box, the broader browser support, running times and debugging capabilities - all made Playwright the obvious choice.
And, while this is very subjective, I like Playwright's syntax much better. I find it similar to React Testing Library's syntax, which I like, and I tend to think the tests are a lot more readable, with the asynchronous aspect of the tests being very straight forward, unlike the syntax of Cypress that can cause tests to feel bloated by .then() statements and subsequent indentations.

I think my favorite feature of Playwright is their implementation of Test Fixtures. They provide a clean way to initialize and reuse resources like page objects, making tests more modular and maintainable. Make sure to check out the above link to learn more about it!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tanstack Table vs AG Grid

Lowlight: (Starting with) Tanstack Table

First off, let me clarify — @tanstack/react-table is a fantastic tool, which is why I was inclined to choose it in the first place, but it wasn’t the best fit for my particular use case. The very features that make it great, like its small bundle size and customizable API, ended up being less relevant to our needs than I originally thought. Despite having full control of the rendering of the Table, I was having some issues aligning its scrolling behavior to our desired outcome (why is it still not possible in 2024 to have a

element with dynamic sizing and scrolling on its body only, without resorting to clunky solutions? ?).

I soon realized that to deliver my feature fast and provide a good user experience, I needed a table with built-in features like pagination, column resizing and row auto-sizing, and I preferred having those out of the box over full control of the UI rendering. Additionally, since the table only appears after a query is run, I could lazy load it, making the bundle size less of a concern.

I highly recommend using the AG Grid theme builder to customize AG Grid according to your preferences/design system. And, for those using Cypress for their testing purposes - I found this cool plugin that abstracts AG Grid to easily interact with it in tests (sadly I could not find the same for Playwright ?)

Final thoughts

Looking back, I definitely feel a sense of pride in what we’ve accomplished. Not every decision was perfect, but taking the time to research and find the most fitting solution was worth it. And when things didn’t go as planned - it challenged us to think critically and adapt quickly, which is important no less.

Please let me know in the comments if there’s something you’d like to see explored further in future articles.
Here’s to more lessons learned, personal growth and having fun along the way ?

以上是從零到發布:我們 Remix 驅動的開發之旅的主要收穫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn