Heim  >  Artikel  >  Web-Frontend  >  Von Null bis zur Veröffentlichung: Die wichtigsten Erkenntnisse aus unserer Remix-basierten Entwicklungsreise

Von Null bis zur Veröffentlichung: Die wichtigsten Erkenntnisse aus unserer Remix-basierten Entwicklungsreise

王林
王林Original
2024-09-07 00:01:08442Durchsuche

Vor etwa sechs Monaten habe ich eine mutige Entscheidung getroffen, die manche als mutig bezeichnen würden, indem ich mich für Remix als Grundlage für die Webanwendung unseres Unternehmens entschieden habe. Spulen wir heute vor, und ich denke, es ist an der Zeit, einen Schritt zurückzutreten und über die Entscheidungen nachzudenken, die wir getroffen haben. Ich werde die wichtigsten getroffenen Infrastrukturentscheidungen durchgehen und nebenbei ein paar praktische Anwendungsbeispiele einstreuen.

Lassen Sie uns also ohne weitere Umschweife direkt auf die Höhepunkte und Tiefpunkte dieser Reise eingehen – eine Mischung aus Zufriedenheit und gewonnenen Erkenntnissen.

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

Remix (oder sollte ich React Router sagen?)

Highlight: Remix

Dies ist wahrscheinlich die „risikoreichste“ Infrastrukturentscheidung, die ich damals getroffen habe, da Remix nicht annähernd so beliebt war wie NextJS und es meines Wissens nicht viele Beispiele dafür gab, dass große Unternehmen Remix verwendeten.
Schneller Vorlauf bis heute – ChatGPT ist erst vor ein paar Tagen von Next zu Remix migriert!

Wie ich in meinem vorherigen Artikel ausführlich dargelegt habe, habe ich mich aus vielen Gründen für Remix entschieden, einige davon sind die Einfachheit, der „Full-Stack“-Aspekt (nämlich die Verwendung des Remix-Servers als „Backend für Frontend“) und seine großartigen Abstraktionen für Routing, Datenabruf und Mutationen.

Zum Glück hat Remix geliefert? Das Framework ist intuitiv, leicht zu erlernen und anderen beizubringen und stellt sicher, dass Best Practices verwendet werden, sodass sowohl das Schreiben von Code als auch das Testen unkompliziert sind.

Ein paar Monate nach Beginn der Zusammenarbeit mit Remix kündigten sie die offizielle Fusion mit React Router an, die hoffentlich noch mehr Menschen davon überzeugen wird, es zu nutzen, genau wie ihr Wechsel zu Vite.

Mir wurde bei vielen Gelegenheiten klar, dass Remix die richtige Entscheidung war. Ich gebe ein praktisches Beispiel, das ich kürzlich in Angriff genommen habe: die Verwendung einer einzelnen Logger-Instanz im Remix-Server, um Aktionen und Fehler in der gesamten App protokollieren und verfolgen zu können, um unsere Überwachungsfähigkeiten zu verbessern. Die Umsetzung war sehr unkompliziert:

Schritt 1 – Erstellen Sie Ihren Logger (in meinem Fall habe ich Winston verwendet, was hervorragend mit Datadog funktioniert, das wir zur Überwachung verwenden)

Schritt 2 – Fügen Sie Ihren Logger zum Ladekontext des Servers hinzu (in meinem Fall war es Express):

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

Schritt 3 (für Typescript-Benutzer) – Aktualisieren Sie die Standardtypdefinitionen von Remix, um den Logger in den App-Ladekontext einzubeziehen

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

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

Schritt 4 – verwenden Sie den Logger nach Ihren Wünschen im Loader oder in der Aktion jeder Route!

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

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

Bevor wir diesen Abschnitt abschließen, möchte ich sagen, dass es auch Dinge gibt, die ich mir bei Remix gewünscht hätte, die aber noch nicht vorhanden sind, wie eine Implementierung von RSC für das Streaming von Daten/Komponenten und Route Middlewares, die sich hervorragend für die Authentifizierung eignen würden /Genehmigung. Glücklicherweise sieht es so aus, als ob diese Dinge (und andere coole Funktionen) in ihrer Roadmap Priorität haben, also hoffen wir, dass wir sie bald bekommen können!

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

Tanstack-Abfrage. Ein absoluter Favorit

Highlight: Abfrage reagieren

Aufgrund meiner positiven Erfahrungen in der Vergangenheit fiel mir die Entscheidung für @tanstack/react-query leicht und ich wurde auch dieses Mal nicht enttäuscht. Die API ist vielseitig, erweiterbar und auf die beste Weise uneinsichtig – was die Integration mit anderen Tools erleichtert.

Es gefällt mir so gut, dass ich mich dafür entschieden habe, da ich wusste, dass unsere interne API auf GraphQL basiert, und nicht die offensichtlichere Wahl, nämlich Apollo Client. Dafür gibt es viele Gründe: Tanstack Query verfügt über eine hervorragende API, ist wesentlich leichter als Apollo und ich wollte mich nicht auf ein Tool verlassen, das stark auf eine bestimmte Technologie wie GraphQL zugeschnitten ist, falls wir es jemals brauchen sollten andere Technologien wechseln oder integrieren.

Da wir außerdem Remix verwenden, konnte ich die SSR-Funktionen von Tanstack Query vollständig nutzen – Abfragen auf der Serverseite vorab abrufen und gleichzeitig die Möglichkeit behalten, diese Abfragen auf der Clientseite zu ändern, ungültig zu machen oder erneut abzurufen. Hier ist ein vereinfachtes Beispiel:

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 ?

Das obige ist der detaillierte Inhalt vonVon Null bis zur Veröffentlichung: Die wichtigsten Erkenntnisse aus unserer Remix-basierten Entwicklungsreise. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn