Maison  >  Article  >  interface Web  >  De zéro au lancement : points à retenir de notre parcours de développement basé sur Remix

De zéro au lancement : points à retenir de notre parcours de développement basé sur Remix

王林
王林original
2024-09-07 00:01:08430parcourir

Il y a environ six mois, j'ai pris ce que certains qualifieraient de décision audacieuse en choisissant Remix comme base de l'application Web de notre entreprise. Avance rapide jusqu’à aujourd’hui, et je pense qu’il est temps de prendre du recul et de réfléchir aux choix que nous avons faits. Je passerai en revue les principales décisions prises en matière d'infrastructure et saupoudrerai quelques exemples d'utilisation pratiques en chemin.

Alors, sans plus tarder, passons directement aux points forts et aux points faibles de ce voyage – un mélange de satisfaction et de leçons apprises.

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

Remix (ou devrais-je dire React Router ?)

Point culminant : Remix

C'est probablement la décision d'infrastructure la plus "risquée" que j'ai prise à cette époque, car Remix n'était pas aussi populaire que NextJS et il n'y avait pas beaucoup d'exemples de grandes entreprises utilisant Remix à ma connaissance.
Avance rapide jusqu'à aujourd'hui : ChatGPT a migré de Next vers Remix il y a quelques jours !

Comme je le détaille dans mon article précédent, j'ai choisi Remix pour de nombreuses raisons, certaines étant sa simplicité, l'aspect "full-stack" (à savoir, utiliser le serveur remix comme "backend pour frontend") et ses grandes abstractions pour routage, récupération de données et mutations.

Heureusement, Remix livré ? Le cadre est intuitif, facile à apprendre et à enseigner aux autres et garantit que les meilleures pratiques sont utilisées, ce qui rend l'écriture du code et son test simples.

Quelques mois après avoir travaillé avec Remix, ils ont annoncé la fusion officielle avec React Router, qui, je l'espère, persuadera encore plus de personnes de l'utiliser, tout comme leur passage à vite l'a fait.

Il m'est apparu clairement à plusieurs reprises que Remix était la bonne décision. Je vais donner un exemple pratique que j'ai abordé récemment : utiliser une seule instance d'enregistreur dans le serveur de remix pour pouvoir enregistrer et tracer les actions et les erreurs dans l'ensemble de l'application afin d'améliorer nos capacités de surveillance. La mise en œuvre a été très simple :

Étape 1 - créez votre enregistreur (dans mon cas j'ai utilisé Winston, qui fonctionne très bien avec Datadog que nous utilisons pour la surveillance)

Étape 2 - ajoutez votre logger au contexte de chargement du serveur (dans mon cas c'était express) :

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

Étape 3 (pour les utilisateurs dactylographiés) - mettre à jour les définitions de type par défaut de Remix pour inclure l'enregistreur dans le contexte de chargement de l'application

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

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

Étape 4 : utilisez l'enregistreur comme vous le souhaitez dans le chargeur ou l'action de n'importe quel itinéraire !

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

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

Avant de conclure cette section, je tiens à dire qu'il y a aussi des choses que j'aurais aimé que Remix ait mais ce n'est pas encore le cas, comme une implémentation de RSC pour le streaming de données/composants, et des Route Middlewares qui seraient parfaits pour l'authentification. /autorisation. Heureusement, il semble que ces éléments (et d'autres fonctionnalités intéressantes) soient prioritaires dans leur feuille de route, alors j'espère que nous pourrons les obtenir bientôt !

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

Requête Tanstack. Un favori de tous les temps

Point culminant : Requête de réaction

Choisir @tanstack/react-query a été une décision facile pour moi, basée sur mes expériences positives passées, et cela n'a pas déçu cette fois non plus. L'API est polyvalente, extensible et sans opinion de la meilleure façon possible, ce qui la rend facile à intégrer à d'autres outils.

Je l'aime tellement que je l'ai choisi sachant que notre API interne est basée sur GraphQL, au lieu du choix plus évident qu'est Apollo Client. Il y a plusieurs raisons à cela : Tanstack Query possède une excellente API, elle est nettement plus légère qu'Apollo, et parce que je ne voulais pas dépendre d'un outil fortement adapté à une technologie spécifique comme GraphQL, au cas où nous aurions besoin de le faire. changer ou intégrer d’autres technologies.

De plus, puisque nous utilisons Remix, j'ai pu utiliser pleinement les capacités SSR de Tanstack Query — prélecture des requêtes côté serveur tout en conservant la possibilité de muter, d'invalider ou de récupérer ces requêtes côté client. Voici un exemple simplifié :

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 ?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Parlons de MutationObserverArticle suivant:Parlons de MutationObserver