Rumah > Artikel > hujung hadapan web > Dari Sifar hingga Pelancaran: Pengambilan Utama daripada Perjalanan Pembangunan Dikuasakan Remix Kami
Sekitar enam bulan yang lalu, saya membuat apa yang dikatakan oleh sesetengah orang sebagai keputusan berani dengan memilih Remix sebagai asas untuk aplikasi web syarikat kami. Cepat ke hari ini, dan saya rasa sudah tiba masanya untuk berundur dan merenung pilihan yang kami buat. Saya akan meneliti keputusan infrastruktur utama yang dibuat dan taburkan sedikit contoh penggunaan praktikal dalam perjalanan.
Jadi, tanpa berlengah lagi, mari kita beralih terus ke sorotan dan sorotan perjalanan ini — gabungan kepuasan dan pengajaran yang dipelajari.
Ini mungkin keputusan infrastruktur "paling berisiko" yang saya buat pada masa itu, kerana Remix tidak begitu popular seperti NextJS dan tidak banyak contoh perusahaan besar menggunakan Remix untuk pengetahuan saya.
Cepat ke hari ini - ChatGPT berhijrah dari Next to Remix hanya beberapa hari yang lalu!
Seperti yang saya perincikan dalam artikel saya sebelum ini, saya memilih Remix atas banyak sebab, beberapa kerana kesederhanaannya, aspek "tindan penuh" (iaitu, menggunakan pelayan remix sebagai "belakang untuk bahagian hadapan") dan abstraksinya yang hebat untuk penghalaan, pengambilan data dan mutasi.
Nasib baik, Remix dihantar ? Rangka kerja ini intuitif, mudah dipelajari dan diajar orang lain serta memastikan amalan terbaik digunakan, menjadikan kedua-dua kod penulisan dan mengujinya dengan mudah.
Beberapa bulan selepas bekerja dengan Remix, mereka mengumumkan penggabungan rasmi dengan React Router, yang saya harap akan memujuk lebih ramai orang untuk menggunakannya, sama seperti langkah mereka ke vite lakukan.
Menjadi jelas kepada saya dalam banyak keadaan bahawa Remix adalah panggilan yang betul. Saya akan memberikan satu contoh praktikal yang saya tangani sejak kebelakangan ini - menggunakan satu contoh logger dalam pelayan remix untuk dapat melog dan mengesan tindakan dan ralat merentas keseluruhan apl untuk meningkatkan kebolehan pemantauan kami. Pelaksanaannya sangat lurus ke hadapan:
Langkah 1 - buat pembalak anda (dalam kes saya, saya menggunakan winston, yang berfungsi hebat dengan Datadog yang kami gunakan untuk pemantauan)
Langkah 2 - tambahkan pembalak anda pada konteks beban pelayan (dalam kes saya, ia adalah ekspres):
app.all( '*', createRequestHandler({ getLoadContext: () => ({ logger, // add any other context variables here }), mode: MODE, // ... }), );
Langkah 3 (untuk pengguna skrip taip) - kemas kini definisi jenis lalai Remix untuk memasukkan pembalak dalam konteks pemuatan apl
import '@remix-run/node'; import { type Logger } from 'winston'; declare module '@remix-run/node' { interface AppLoadContext { logger: Logger; } }
Langkah 4 - gunakan pembalak mengikut kehendak anda dalam mana-mana pemuat atau tindakan laluan!
export async function action({ request, context }: ActionFunctionArgs) { try { await someAction(); } catch (e) { context.logger.error(e); } }
Sebelum kita menyimpulkan bahagian ini, saya ingin mengatakan bahawa terdapat juga perkara yang saya harap Remix ada tetapi masih belum, seperti pelaksanaan RSC untuk penstriman data/komponen, dan Route Middlewares yang sesuai untuk pengesahan /keizinan. Nasib baik, nampaknya perkara ini (dan ciri hebat yang lain) diutamakan dalam peta jalan mereka, jadi mudah-mudahan kami boleh mendapatkannya tidak lama lagi!
Memilih @tanstack/react-query merupakan keputusan yang mudah untuk saya, berdasarkan pengalaman positif saya yang lalu, dan kali ini juga tidak mengecewakan. API adalah serba boleh, boleh dipanjangkan dan tidak mempunyai pendapat dengan cara terbaik — menjadikannya mudah untuk disepadukan dengan alatan lain.
Saya sangat menyukainya sehingga saya memilihnya kerana mengetahui API dalaman kami berasaskan GraphQL, bukannya pilihan yang lebih jelas iaitu Apollo Client. Terdapat banyak sebab mengapa: Tanstack Query mempunyai API yang sangat baik, ia jauh lebih ringan daripada Apollo, dan kerana saya tidak mahu bergantung pada alat yang sangat disesuaikan dengan teknologi tertentu seperti GraphQL, sekiranya kita perlu menukar atau menggabungkan teknologi lain.
Selain itu, memandangkan kami menggunakan Remix, saya boleh menggunakan sepenuhnya keupayaan SSR Tanstack Query — pra-mengambil pertanyaan pada bahagian pelayan sambil mengekalkan keupayaan untuk mengubah, membatalkan atau mengambil semula pertanyaan ini pada bahagian klien. Berikut ialah contoh ringkas:
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 /> ); }
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!
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!
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