Πώς έφτιαξα μια μοντέρνα ιστοσελίδα το 2021

Αυτό το άρθρο είναι μία μετάφραση του “How I built a modern website in 2021” από τον Kent C. Dodds.

Photo by Kari Shea

Για περισσότερο από το μισό 2021, δούλεψα σε μια πλήρη επανεγγραφή του kentcdodds.com. Αυτό το άρθρο το διαβάζετε στην νέα ιστοσελίδα! Χρησιμοποιείτε σκοτεινή ή φωτεινή λειτουργία;

Έχετε ήδη συνδεθεί και επιλέξει την ομάδα σας; Έχετε προσπαθήσει να καλέσετε στο the Call Kent Podcast; Αυτή η ανάρτηση δεν αφορά τις δυνατότητες του νέου ιστότοπου, αλλά πώς τον έχτισα. Σε μια μόνο ανάρτηση θα ήταν πολύ δύσκολο να αναφερθώ σε όλα αυτά. Θέλω απλώς να σας δώσω μια επισκόπηση των τεχνολογιών και των βιβλιοθηκών που χρησιμοποίησα για να δημιουργήσω αυτήν την εμπειρία για εσάς.

Εάν δεν το έχετε διαβάσει ήδη, παρακάτω θα βρείτε μια γενική επισκόπηση για το τι μπορεί να κάνει αυτός ο ιστότοπος για την εκμάθηση και την εξέλιξή σας ως μηχανικός λογισμικού διαβάζοντας πρώτα το “Introducing the new kentcdodds.com”.

Επισκόπηση στατιστικών

Πρώτον, για να σας δώσω μια ιδέα για την κλίμακα που μιλάμε εδώ.

Ακολουθούν λοιπόν μερικά στατιστικά:

Κατά τη στιγμή της σύνταξης αυτού του άρθρου (Σεπτέμβριος 2021) αυτά είναι τα στατιστικά στοιχεία που δίνει το `cloc`:

cloc stats

26 χιλιάδες γραμμές κώδικα δεν μοιάζουν με το project που δουλεύετε στη δουλειά σας, όπου μισή ντουζίνα από διαφορετικές ομάδες προσθέτουν κώδικα τα τελευταία 8 χρόνια, αλλά δεν μοιάζει ούτε με το απλό blog σας. Αυτή είναι μια full-stack διαδικτυακή εφαρμογή με βάση δεδομένων, προσωρινή μνήμη, λογαριασμούς χρηστών κ.λπ. Είμαι αρκετά βέβαιος ότι αυτή είναι η μεγαλύτερη εφαρμογή Remix που υπάρχει αυτήν τη στιγμή.

Δεν το έκανα μόνος μου όλο αυτό. Μπορείτε να δείτε τη σελίδα επαίνων για λεπτομέρειες σχετικά με τους συντελεστές. Εγώ ήμουν ο κύριος προγραμματιστής και πήρα όλες τις αρχιτεκτονικές αποφάσεις (και λάθη; μόνο ο χρόνος θα δείξει 😅) για τον ιστότοπο.

Το πρώτο commit ήταν τον Νοέμβριο του 2020. Το μεγαλύτερο μέρος της ανάπτυξη πραγματοποιήθηκε τους τελευταίους 3–4 μήνες. Υπάρχουν περίπου 825 commits μέχρι στιγμής 😅.

Επισκόπηση τεχνολογιών

Ακολουθούν οι κύριες τεχνολογίες που χρησιμοποιούνται σε αυτό το έργο (χωρίς ιδιαίτερη σειρά):

  • React: Για το UI
  • Remix: Το framework για το Client/Server/Routing
  • TypeScript: Typed JavaScript (απαραίτητο για οποιοδήποτε έργο σκοπεύετε να συντηρήσετε)
  • XState: State machine εργαλείο που απλοποιεί τη σύνθετη διαχείριση των states στα components
  • Prisma: Φανταστικό ORM με πολλά migrations και υποστηρίζει και TypeScript client
  • Express: Node server framework
  • Cypress: E2E framework για testing
  • Jest: Unit/Component framework για testing
  • Testing Library: Απλά βοηθητικά προγράμματα για DOM-based testing των διεπαφών.
  • MSW: Φανταστικό εργαλείο για προσομοίωση των HTTP requests σε browser/node
  • Tailwind CSS: Βοηθητικές classes για σταθερό/διατηρήσιμο styling
  • Postcss: CSS processor (το χρησιμοποίησα απλά για το autoprefixer και tailwind)
  • Reach UI: Ένα σύνολο προσβάσιμων UI components που χρειάζεται κάθε εφαρμογή (accordion/tabs/dialog/etc…)
  • ESBuild: Χρήσιμο για την ομαδοποίηση JavaScript αρχείων (χρησιμοποιείται από το Remix και το mdx-bundler).
  • mdx-bundler: Εργαλείο για τη συγκέντρωση και ομαδοποίηση MDX για το περιεχόμενο του blog μου (για τις αναρτήσεις του blog και μερικών άλλων απλών σελίδων).
  • Octokit: Βιβλιοθήκη που διευκολύνει την αλληλεπίδραση με το GitHub API.
  • Framer Motion: Πολύ καλή βιβλιοθήκη για React Animatios
  • Unified: Σύστημα για Markdown/HTML parser/transformer/compiler
  • Postgres: Βάση δεδομένων SQL
  • Redis: Aποθήκευση δομής δεδομένων στη μνήμη

Ακολουθούν οι υπηρεσίες που χρησιμοποιεί αυτός ο ιστότοπος:

  • Fly.io: Πλατφόρμα hosting
  • GitHub Actions: Τα GitHub Actions διευκολύνουν την αυτοματοποίηση όλων των ροών εργασίας λογισμικού με CI/CD.
  • Sentry: Υπηρεσία αναφοράς σφαλμάτων
  • Cloudinary: Φανταστική υπηρεσία φιλοξενίας και μεταμόρφωσης εικόνας.
  • Fathom: Υπηρεσία ηθικής ανάλυσης που εστιάζει στην ιδιωτικότητα.
  • Metronome: Υπηρεσία μετρήσεων Remix

Eπισκόπηση αρχιτεκτονικής

Διαδικασία που καθιστά το λογισμικό διαθέσιμο για χρήση

Deployment pipeline

Νομίζω ότι μπορεί να είναι αρκετά διδακτικό για τη συνολική αρχιτεκτονική να περιγράψουμε πώς αναπτύσσεται μια εφαρμογή. Το παραπάνω διάγραμμα Excalidraw το περιγράφει οπτικά. Επιτρέψτε μου να το περιγράψω και σε γραπτή μορφή:

Πρώτον, κάνω commit μια αλλαγή στο τοπικό repo. Στη συνέχεια, προωθώ τις αλλαγές μου στο (ανοιχτού κώδικα) GitHub repo. Από εκεί, έχω δύο GitHub Actions που εκτελούνται αυτόματα σε κάθε push στο main branch.

Ο κύκλος “Discord” απλώς δηλώνει ότι έχω εγκαταστήσει ένα GitHub webhook για το Discord, οπότε κάθε επιτυχία/αποτυχία θα έχει ως αποτέλεσμα ένα μήνυμα σε ένα κανάλι ώστε να μπορώ να παρακολουθώ εύκολα πώς πηγαίνουν τα πράγματα ανά πάσα στιγμή.

GitHub Actions: 🥬 Ανανέωση περιεχομένου

Το πρώτο GitHub action ονομάζεται “🥬 Ανανέωση περιεχομένου” και προορίζεται να ανανεώσει οποιοδήποτε περιεχόμενο μπορεί να έχει αλλάξει. Πριν περιγράψω τι κάνει, επιτρέψτε μου να εξηγήσω το πρόβλημα που λύνει. Η προηγούμενη έκδοση του kentcdodds.com γράφτηκε με Gatsby και λόγω της φύσης του SSG του Gatsby κάθε φορά που ήθελα να κάνω μια αλλαγή περιεχομένου, θα έπρεπε να ξαναφτιάξω ολόκληρο τον ιστότοπό μου (που θα μπορούσε να διαρκέσει από 10–25 λεπτά).

Αλλά τώρα που έχω server και χρησιμοποιώ SSR, δεν χρειάζεται να περιμένω μια πλήρη ανακατασκευή για να ανανεώσω το περιεχόμενό μου. Ο server μου μπορεί να έχει πρόσβαση σε όλο το περιεχόμενο απευθείας από το GitHub μέσω του GitHub API. Το πρόβλημα είναι ότι αυτό προσθέτει παραπάνω καθυστερήσεις για κάθε request του ιστολογίου μου. Προσθέστε σε αυτόν τον χρόνο την μεταγλώτισση του κώδικα σε MDX και έχετε ένα πολύ αργό ιστολόγιο. Έχω, λοιπόν, μια προσωρινή μνήμη Redis για όλα αυτά. Το πρόβλημα των κρυφών μνημών είναι το να τα κάνεις invalidate. Πρέπει να βεβαιωθώ πως όταν κάνω μια ενημέρωση σε κάποιο περιεχόμενο, η προσωρινή μνήμη Redis ανανεώνεται.

Και αυτό κάνει το πρώτο GitHub action. Πρώτα καθορίζει όλες τις αλλαγές περιεχομένου που συνέβησαν μεταξύ του commit που γίνεται build και του commit της τελευταίας φοράς που έγινε ανανέωση (αυτή η τιμή αποθηκεύεται στο redis και o server μου προσφέρει ένα endpoint για την ανάκτησή της πληροφορίας). Εάν κάποιο από τα άλλα αρχεία ήταν στον κατάλογο ./content, τότε η ενέργεια κάνει ένα πιστοποιημένο POST request σε άλλο endpoint του server μου με όλα τα αρχεία περιεχομένου που άλλαξαν. Στη συνέχεια, ο server μου ανακτά όλο το περιεχόμενο από το GitHub API, επανασυνθέτει τις σελίδες MDX και ωθεί την ενημέρωση στην προσωρινή μνήμη Redis, την οποία το Fly.io διαδίδει αυτόματα στις άλλες περιοχές.

Αυτό μειώνει αυτό που χρειαζόταν 10–25 λεπτά στα 8 δευτερόλεπτα. Και μου εξοικονομεί υπολογιστικούς πόρους, καθώς η διόρθωση τυπογραφικού λάθους στο περιεχόμενό μου δεν απαιτεί να ξαναγίνει ανασυγκρότηση, επανατοποθέτηση και καθαρισμός της προσωρινή μνήμης ολόκληρου του ιστότοπου.

Συνειδητοποιώ ότι η χρήση του GitHub ως CMS μου είναι λίγο περίεργη, αλλά υπέροχοι άνθρωποι σαν εσάς συμβάλλουν σε βελτιώσεις στο περιεχόμενο ανοιχτού κώδικά μου όλη την ώρα και το εκτιμώ. Έτσι, διατηρώντας τα πράγματα στο GitHub, αυτό μπορεί να συνεχιστεί. (Σημειώστε τον σύνδεσμο επεξεργασίας στο κάτω μέρος κάθε ανάρτησης).

GitHub Actions: 🚀 Παράδοση νέου περιεχομένου

Το δεύτερο GitHub action κάνει διαθέσιμες τις αλλαγές στον ιστότοπο. Κατ ‘αρχάς, καθορίζει εάν οι αλλαγές μπορούν να γίνουν. Εάν το μόνο που άλλαξε ήταν το περιεχόμενο, τότε δεν χρειάζεται να ξαναγίνει όλη η προηγούμενη διαδικασία χάρη στην “🥬 Ανανέωση περιεχομένου”. Η συντριπτική πλειοψηφία των commits μου στον παλιό μου ιστότοπο ήταν αλλαγές μόνο στο περιεχόμενο, επομένως αυτό βοηθά στην διάσωση των δέντρων 🌲🌴🌳

Μόλις διαπιστωθεί ότι έχουμε αλλαγές που πρέπει να παραδοθούν τότε ξεκινάμε παράλληλα πολλά βήματα:

  • ⬣ ESLint: Γίνεται linting στο project για μικρά λάθη
  • ʦ TypeScript: Γίνονται έλεγχη για type errors
  • 🃏 Jest: Τρέχουν component και unit tests
  • ⚫️ Cypress: Τρέχουν τα end-to-end tests
  • 🐳 Build: Γίνεται build το docker image

Το βήμα Cypress παραλληλίζεται περαιτέρω με τη διαίρεση των E2E tests σε τρία μεμονωμένα containers που χωρίζουν τα tests μεταξύ τους για να εκτελεστούν όσο το δυνατόν γρηγορότερα.

Ίσως παρατηρήσετε ότι δεν υπάρχουν βέλη από το βήμα του Cypress. Αυτό είναι σκόπιμο και προσωρινό. Προς το παρόν δεν αποτυγχάνω στο build εάν αποτύχουν τα E2E tests. Μέχρι στιγμής, δεν έχω ανησυχήσει για την παράδοση κώδικα που έχει χαλάσει και δεν ήθελα να σταματήσω μια παράδοση επειδή κατά λάθος έσπασα κάτι για τους 0 χρήστες που περιμένουν ότι τα πράγματα θα λειτουργήσουν. Τα E2E tests είναι επίσης το πιο αργό μέρος του pipeline και θέλω να παραδόσω τα πράγματα γρήγορα. Σύντομα μάλλον θα με νοιάζει περισσότερο αν θα σπάσω τον ιστότοπο, αλλά προς το παρόν θα προτιμούσα τα πράγματα να αναπτυχθούν γρηγορότερα. Ξέρω πότε αποτυγχάνουν αυτές οι δοκιμές.

Μόλις ολοκληρωθούν επιτυχώς τα ESLint,TypeScript, Jest και το Build, τότε μπορούμε να προχωρήσουμε στο βήμα παράδοσης. Για εμένα αυτό το κομμάτι είναι απλό. Απλώς χρησιμοποιώ το Fly CLI για να αναπτύξω το docker container που δημιουργήθηκε στο βήμα κατασκευής. Από εκεί το Fly φροντίζει τα υπόλοιπα. Ξεκινάει το docker container σε κάθε μία από τις περιοχές που έχω ρυθμίσει για τον Node server: Ντάλας, Σαντιάγο, Σίδνεϊ, Χονγκ Κονγκ, Τσενάι και Άμστερνταμ. Όταν είναι έτοιμοι να λάβουν επισκεψιμότητα, το Fly αλλάζει την κυκλοφορία στο νέο instance και στη συνέχεια κλείνει το παλιό. Εάν υπάρχει αποτυχία εκκίνησης σε οποιαδήποτε περιοχή, κάνει αυτόματα roll back.

Επιπλέον, αυτό το βήμα της παράδοσης χρησιμοποιεί τη λειτουργία μετεγκατάστασης του prisma για να εφαρμόσει τις μετακινήσεις που έχω δημιουργήσει από την τελευταία μετεγκατάσταση (αποθηκεύει πληροφορίες για την τελευταία μετεγκατάσταση στην Postgres μου). Το Prisma εκτελεί τη μετεγκατάσταση στο instance του Ντάλας και το Fly διαδίδει αυτόματα αυτές τις αλλαγές σε όλες τις άλλες περιοχές αμέσως.

Και αυτό συμβαίνει όταν λέω: git push ή κάντε κλικ στο κουμπί “Merge”😄

Συνδεσιμότητα βάσης

Database connectivity

Ένα από τα πιο ωραία πράγματα του Fly.io (και ο λόγος που επέλεξα το Fly έναντι των εναλλακτικών Node server hosts) είναι η δυνατότητα ανάπτυξης της εφαρμογής σας σε πολλές περιοχές σε όλο τον κόσμο. Έχω επιλέξει 6 με βάση τα αναλυτικά στοιχεία από τον προηγούμενο ιστότοπό μου, αλλά έχουν πολλά περισσότερα.

Ωστόσο, η παράδοση απο τον Node server σε πολλές περιοχές είναι μόνο μέρος της ιστορίας. Για να λάβετε πραγματικά τα οφέλη του δικτύου από τον συνδυασμό, χρειάζεστε επίσης τα δεδομένα σας κοντά. Το Fly υποστηρίζει επίσης τη φιλοξενία Postgres και Redis clusters σε κάθε περιοχή. Αυτό σημαίνει ότι όταν ένας εξουσιοδοτημένος χρήστης στο Βερολίνο πηγαίνει στο “The Call Kent Podcast”, χτυπά τον πλησιέστερο διακομιστή σε αυτόν (Άμστερνταμ) ο οποίος θα ρωτήσει την προσωρινή μνήμη Postgres DB και Redis που βρίσκονται στην ίδια περιοχή, καθιστώντας την όλη εμπειρία εξαιρετικά γρήγορη όπου κι αν βρίσκεται ο χρήστης στον κόσμο.

Επιπλέον, δεν είμαι προσκολλημένος στον vendor. Ανά πάσα στιγμή θα μπορούσα να πάρω τα παιχνίδια μου στο σπίτι και να φιλοξενήσω τον ιστότοπό μου οπουδήποτε αλλού που υποστηρίζει Docker. Αυτός είναι ο λόγος για τον οποίο δεν πήγα με μια λύση όπως το Cloudflare Workers και το FaunaDB. Επιπλέον, δεν χρειάζεται να περιορίσω την εφαρμογή μου στους περιορισμούς αυτών των υπηρεσιών. Είμαι εξαιρετικά χαρούμενος με το Fly και δεν περιμένω να φύγω σύντομα.

Αλλά αυτό δεν σημαίνει ότι όλα αυτά είναι χωρίς συμβιβασμούς (τίποτα δεν είναι). Όλη αυτή η πολυπεριφερειακή ανάπτυξη έρχεται με το πρόβλημα της συνέπειας. Έχω πολλές βάσεις δεδομένων, αλλά δεν θέλω να χωρίσω την εφαρμογή μου ανά περιοχή. Τα δεδομένα πρέπει να είναι τα ίδια σε όλες αυτές τις βάσεις δεδομένων. Πώς μπορώ λοιπόν να εξασφαλίσω συνέπεια;

Λοιπόν, επιλέγουμε μια περιοχή να είναι η κύρια περιοχή μας και, στη συνέχεια, κάνουμε όλες τις άλλες περιοχές διαθέσιμες μόνο για ανάγνωση. (read-only). Ναι, έτσι ο χρήστης στο Βερολίνο δεν θα μπορεί να γράφει στη βάση δεδομένων στο Άμστερνταμ. Αλλά μην ανησυχείτε, όλα τα instances του Node server θα πραγματοποιήσουν μια σύνδεση ανάγνωσης στην πλησιέστερη περιοχή, οπότε η ανάγνωση (μακράν η πιο κοινή λειτουργία) είναι γρήγορη και στη συνέχεια δημιουργούν επίσης μια σύνδεση εγγραφής στην κύρια περιοχή, ώστε να μπορούν να λειτουργούν οι εγγραφές. Mόλις γίνει ενημέρωση στην κύρια περιοχή, το Fly αυτόματα διαδίδει αυτές τις αλλαγές σε όλες τις άλλες περιοχές. Είναι πολύ γρήγορο και λειτουργεί πολύ καλά!

Το Fly το κάνει πολύ εύκολο και είμαι πολύ ευχαριστημένος με αυτό. Υπάρχει ένα άλλο πρόβλημα που δημιουργείται όμως και πρέπει να το αντιμετωπίσουμε.

Επανάληψη των FLY requests

Ένα πρόβλημα με τις συνδέσεις ανάγνωσης/εγγραφής που χρησιμοποιώ για να κάνω την παράδοση σε πολλαπλές περιοχές εξαιρετικά γρήγορη είναι ότι αν ο φίλος μας στο Βερολίνο γράψει στη βάση δεδομένων και στη συνέχεια διαβάσει τα δεδομένα που μόλις έγραψαν, είναι πιθανό να διαβάσει τα παλιά δεδομένα πριν τελειώσει το Fly να ολοκληρώση την ενημέρωση. Η διάδοση δεδομένων κανονικά συμβαίνει σε χιλιοστά του δευτερολέπτου, αλλά σε περιπτώσεις όπου τα δεδομένα είναι μεγάλα (όπως όταν υποβάλλετε μια ηχογράφηση στο “The Call Kent Podcast”), είναι πολύ πιθανό η επόμενη ανάγνωσή σας να κερδίσει το Fly.

Ένας τρόπος για να αποφύγετε αυτό το πρόβλημα είναι να βεβαιωθείτε ότι μόλις ολοκληρώσετε μια εγγραφή, το υπόλοιπο αίτημα εκτελεί τις αναγνώσεις του έναντι της κύριας βάσης δεδομένων. Δυστυχώς αυτό καθιστά τον κώδικα λίγο περίπλοκο.

Μια άλλη προσέγγιση που υποστηρίζει το Fly είναι η “επανάληψη” ενός αιτήματος στην κύρια περιοχή όπου οι συνδέσεις ανάγνωσης και εγγραφής βρίσκονται και οι δύο στην κύρια περιοχή. Το κάνετε αυτό στέλνοντας μια απάντηση στο αίτημα με το header fly-replay: Region = dfw και το Fly θα υποκλέψει αυτήν την απάντηση, θα την εμποδίσει να επιστρέψει στον χρήστη και θα επαναλάβει το ίδιο ακριβώς αίτημα στην καθορισμένη περιοχή (dfwείναι Ντάλας που είναι η κύρια περιοχή μου).

Έχω λοιπόν ένα ενδιάμεσο λογισμικό στην express εφαρμογή που απλώς επαναλαμβάνει αυτόματα όλα τα non-GET αιτήματα. Αυτό σημαίνει ότι αυτά τα αιτήματα θα χρειαστούν λίγο περισσότερο χρόνο για τον φίλο μας στο Βερολίνο, αλλά και πάλι, αυτά τα αιτήματα δεν συμβαίνουν πολύ συχνά και ειλικρινά δεν γνωρίζω κάποια καλύτερη εναλλακτική λύση 🙃.

Είμαι πολύ ευχαριστημένος με αυτή τη λύση!

Ανάπτυξη λογισμικού με MSW

Όταν δουλεύω κάτι τοπικά, έχω τις βάσεις δεδομένων postgres και redis που τρέχουν σε docker container μέσω ενός απλού docker-compose.yml. Αλλά αλληλεπιδρώ επίσης με ένα σωρό API τρίτων. Από τη στιγμή που γράφω αυτό το άρθρο (Σεπτέμβριος 2021), η εφαρμογή μου λειτουργεί με τα ακόλουθα API τρίτων:

  1. api.github.com
  2. oembed.com
  3. api.twitter.com
  4. api.tito.io
  5. api.transistor.fm
  6. s3.amazonaws.com
  7. discord.com/api
  8. api.convertkit.com
  9. api.simplecast.com
  10. api.mailgun.net
  11. res.cloudinary.com
  12. www.gravatar.com/avatar
  13. verifier.meetchopra.com

Είμαι μεγάλος υποστηρικτής του να μπορώ να δουλεύω εντελώς εκτός σύνδεσης 😅. Είναι διασκεδαστικό να ανεβαίνεις στα βουνά χωρίς σύνδεση στο Διαδίκτυο και να μπορείς να δουλεύεις στον ιστότοπό σου (και καθώς πληκτρολογώ αυτό, βρίσκομαι σε αεροπλάνο χωρίς διαδίκτυο). Αλλά με τόσα πολλά API τρίτων, πώς είναι δυνατόν;

Απλό: Τα κάνω mock με το MSW!

Το MSW είναι ένα φανταστικό εργαλείο για να κάνεις fake requests τόσο στον browser όσο και στο node. Για μένα, το 100% των requests τρίτων συμβαίνουν σε Remix loaders στον server, οπότε έχω ρυθμίσει το MSW μόνο στον node server. Αυτό που μου αρέσει στο MSW είναι ότι είναι εντελώς μη επεμβατικό στη βάση δεδομένων μου. Ο τρόπος με τον οποίο το λειτουργώ είναι αρκετά απλός. Δείτε πώς ξεκινά ο server μου:

node .

Και έτσι το ξεκινάω χρησιμοποιώντας τα mocks μου:

node --require ./mocks .

Αυτό είναι. Ο φάκελος ./mocks έχει όλους τους MSW handlers και αρχικοποιεί MSW για να παρεμβάλει στα HTTP requests. Αυτό με βοήθησε πραγματικά να παραμείνω παραγωγικός. Τα mocks μου είναι πολύ πιο γρήγορα από το API και δεν βασίζεται καθόλου στη σύνδεσή μου στο διαδίκτυο. Είναι μια τεράστια νίκη και το προτείνω ανεπιφύλακτα.

Για πολλά από τα API που έχω κάνει mock χρησιμοποιώ απλώς το faker.js ώστε να δημιουργώ τυχαία δεδομένα που συμμορφώνονται στις προδιαγραφές των API. Αλλά για τα GitHub API, πραγματικά τυχαίνει να γνωρίζω ποια θα ήταν η απάντηση ακόμα κι αν δεν είμαι συνδεδεμένος στο διαδίκτυο, επειδή εργάζομαι κυριολεκτικά στο repo από το οποίο θα ζητώ περιεχόμενο! Έτσι, το GitHub API mock μου διαβάζει πραγματικά το σύστημα αρχείων και απαντά με πραγματικό περιεχόμενο. Έτσι δουλεύω τοπικά το περιεχόμενό μου. Και δεν χρειάζεται να κάνω κάτι φανταχτερό στον κώδικά μου. Όσον αφορά την εφαρμογή μου, απλώς στέλνω αιτήματα για το περιεχόμενό μου, αλλά το MSW το υποκλέπτει και μου επιτρέπει να απαντήσω με ό,τι υπάρχει στο σύστημα αρχείων.

Για να προχωρήσουμε ένα βήμα παραπέρα, το Remix επαναφορτώνει αυτόματα τη σελίδα όταν αλλάζουν δεδομένα, οπότε κάθε φορά που υπάρχει αλλαγή στο περιεχόμενο, η προσωρινή μνήμη redis για αυτό το περιεχόμενο ενημερώνεται αυτόματα (ναι, χρησιμοποιώ επίσης την προσωρινή μνήμη redis τοπικά ) και το Remix φορτώσει ξανά τη σελίδα. Αν δεν έχετε καταλάβει ήδη, νομίζω ότι όλο αυτό το πράγμα είναι πολύ καλό.

Και επειδή χρησιμοποιώ αυτό το setup με το MSW για να δουλεύω τοπικά, μπορώ να κάνω τα E2E tests να χρησιμοποιούν το ίδιο πράγμα και να παραμένουν ορθά. Εάν θέλω να εκτελέσω τα E2E tests με τα πραγματικά API, τότε το μόνο που έχω να κάνω έιναι να μην χρησιμοποιήσω το --require ./mocks .

To MSW με κάνει να νιώθω πολύ παραγωγικός και με αυτοπεποίθηση γι’ αυτό που κάνω.

Κρατώντας πράγματα στην μνήμη με το Redis/LRU

Όπως περιγράφηκε νωρίτερα με τα διαγράμματα, χρησιμοποιώ την προσωρινή μνήμη redis μου με το Fly.io. Είναι φοβερό. Αλλά έχω δημιουργήσει τη δική μου μικρή υλοποίηδη για να αλληλεπιδράσω με το redis ώστε να έχω μερικές ενδιαφέρουσες ιδιότητες για τις οποίες πιστεύω ότι αξίζει να μιλήσω.

Πρώτον, τα προβλήματα: Θέλω ο ιστότοπός μου να είναι εξαιρετικά γρήγορος, αλλά θέλω επίσης να κάνω πράγματα που απαιτούν χρόνο σε κάθε request. Κάποια πράγματα που θέλω να κάνω θα μπορούσαν ακόμη και να χαρακτηριστούν ως αργά ή αναξιόπιστα. Έτσι χρησιμοποιώ το Redis για να αποθηκεύσω προσωρινά πράγματα. Αυτό μπορεί να μειώσει τους χρόνους εκτέλεσης απο τα 350ms στα 5ms. Ωστόσο, με την χρήση της προσωρινής μνήμης έρχεται η επιπλοκή του πως την ενημερώνεις ξανά με νέα δεδομένα. Έχω περιγράψει πώς το κάνω αυτό με το περιεχόμενό μου, αλλά αποθηκεύω πολλά περισσότερα πράγματα σε αυτή την μνήμη. Τα περισσότερα API τρίτων αποθηκεύονται προσωρινά και ακόμη και τα αποτελέσματα μερικών request μου στην βάση αποθηκεύονται προσωρινά (το Postgres είναι αρκετά γρήγορο, αλλά σε κάθε σελίδα μου εκτελώ περίπου 30 ερωτήματα).

Δεν αποθηκεύονται τα πάντα στο Redis, ορισμένα πράγματα αποθηκεύονται μέσω της μονάδας lru-cache (το lru σημαίνει “λιγότερο πρόσφατα χρησιμοποιημένο” και βοηθά στην προσωρινή μνήμη σας να αποφύγει σφάλματα μνήμης). Χρησιμοποιώ την προσωρινή μνήμη LRU στη μνήμη για πολύ μικρής διάρκειας τιμές προσωρινής μνήμης, όπως τα ερωτήματα postgres.

Με τόσα πολλά πράγματα που πρέπει να αποθηκευτούν, χρειάστηκε μια υλοποίηση για να γίνει η διαδικασία ακύρωσης απλούστερη και συνεπής. Ήμουν πολύ ανυπόμονος να βρω μια βιβλιοθήκη που θα λειτουργούσε για μένα, οπότε έχτισα τη δική μου.

Εδώ είναι η υλοποίηση:

// Typescripttype CacheMetadata = {
createdTime: number
maxAge: number | null
}
// it's the value/null/undefined or a promise that resolves to that
type VNUP<Value> = Value | null | undefined | Promise<Value | null | undefined>
async function cachified<
Value,
Cache extends {
name: string
get: (key: string) => VNUP<{
metadata: CacheMetadata
value: Value
}>
set: (
key: string,
value: {
metadata: CacheMetadata
value: Value
},
) => unknown | Promise<unknown>
del: (key: string) => unknown | Promise<unknown>
},
>(options: {
key: string
cache: Cache
getFreshValue: () => Promise<Value>
checkValue?: (value: Value) => boolean
forceFresh?: boolean
request?: Request
fallbackToCache?: boolean
timings?: Timings
timingType?: string
maxAge?: number
}): Promise<Value> {
// do the stuff...
}
// here's an example of the cachified credits.yml that powers the /credits page:
async function getPeople({
request,
forceFresh,
}: {
request?: Request
forceFresh?: boolean
}) {
const allPeople = await cachified({
cache: redisCache,
key: 'content:data:credits.yml',
request,
forceFresh,
maxAge: 1000 * 60 * 60 * 24 * 30,
getFreshValue: async () => {
const creditsString = await downloadFile('content/data/credits.yml')
const rawCredits = YAML.parse(creditsString)
if (!Array.isArray(rawCredits)) {
console.error('Credits is not an array', rawCredits)
throw new Error('Credits is not an array.')
}
return rawCredits.map(mapPerson).filter(typedBoolean)
},
checkValue: (value: unknown) => Array.isArray(value),
})
return allPeople
}

Είναι πολλές οι επιλογές 😶. Αλλά μην ανησυχείτε, θα σας καθοδηγήσω. Ας ξεκινήσουμε με τους γενικούς τύπους:

  • To Value αναφέρεται στην τιμή που πρέπει να αποθηκευτεί/ανακτηθεί από την προσωρινή μνήμη
  • Η προσωρινή μνήμη (Cache) είναι απλώς ένα object που έχει name(για καταγραφή) και μεθόδους get, setκαι del.
  • Το CacheMetadata είναι πληροφορίες που αποθηκεύονται μαζί με την τιμή για να καθοριστεί πότε πρέπει να ανανεωθεί η τιμή.

Και τώρα για τις επιλογές:

  • key είναι το αναγνωριστικό για την τιμή.
  • cache είναι η προσωρινή μνήμη
  • Η getFreshValue είναι η συνάρτηση που ανακτά την τιμή. Αυτό θα τρέχαμε κάθε φορά αν δεν είχαμε μια προσωρινή μνήμη. Μόλις λάβουμε την νέα τιμή, αυτή η τιμή ορίζεται στην προσωρινή μνήμη στο κλειδί.
  • Το checkValue είναι μια συνάρτηση που επαληθεύει ότι η τιμή που ανακτήθηκε από την προσωρινή μνήμη/getFreshValue. Είναι πιθανό να αναπτύξω μια αλλαγή στη getFreshValue που αλλάζει την τιμή και εάν η τιμή στην προσωρινή μνήμη δεν είναι σωστή, τότε θέλουμε να αναγκάσουμε να κληθεί το getFreshValue για να αποφευχθούν σφάλματα τύπου εκτέλεσης. Το χρησιμοποιούμε επίσης αυτό για να ελέγξουμε ότι αυτό που πήραμε από την getFreshValue είναι σωστό και αν δεν είναι τότε πετάμε ένα βοηθητικό μήνυμα σφάλματος (σίγουρα καλύτερο από ένα type error).
  • forceFresh είναι αυτό που νομίζεις. Παραλείπει να κοιτάξει την προσωρινή μνήμη και θα καλέσει την getFreshValue ακόμη και αν η τιμή δεν έχει λήξει ακόμα.
  • Το request χρησιμοποιείται για τον προσδιορισμό της προεπιλεγμένης τιμής του forceFresh. Εάν το αίτημα έχει την παράμετρο ?fresh και ο χρήστης έχει το ρόλο του ADMIN (άρα … μόνο εγώ), τότε το forceFresh θα είναι προεπιλεγμένο ως true. Αυτό μου επιτρέπει να ανανεώσω με μη αυτόματο τρόπο την προσωρινή μνήμη για όλους τους πόρους σε οποιαδήποτε σελίδα. Δεν χρειάζεται να το κάνω πολύ συχνά όμως.
  • fallbackToCache εάν προσπαθούσαμε να αναγκάσουμε το ForceFresh (έτσι παραλείψαμε την προσωρινή μνήμη) και η νέα τιμή απέτυχε, τότε μπορεί να θέλουμε να επιστρέψουμε στην προσωρινή μνήμη αντί να πετάξουμε σφάλμα στην σελίδα. Αυτό ελέγχει το fallbackToCache και ως προεπιλογή είναι true.
  • timings and timingsType χρησιμοποιούνται για ένα άλλο βοηθητικό πρόγραμμα που έχω για να παρακολουθώ τον χρόνο που χρειάζονται τα πράγματα, τα οποία στη συνέχεια αποστέλλονται πίσω στo header Server-Timing (χρήσιμο για τον εντοπισμό τέτοιων συμφόρων).
  • Το maxAge ελέγχει πόσο χρόνο διατηρείται η προσωρινή μνήμη πριν προσπαθήσουμε να την ανανεώσουμε αυτόματα.

Όταν μια τιμή διαβάζεται από την προσωρινή μνήμη, επιστρέφουμε την τιμή αμέσως για να διατηρήσουμε τα πράγματα γρήγορα. Μετά την αποστολή του αιτήματος, καθορίζουμε εάν η προσωρινή μνήμη έχει λήξει και αν έχει, καλούμε ξανά την cachifiedμε το forceRefresh να είναι true.

Αυτό έχει ως αποτέλεσμα κανένας χρήστης να μην χρειάζεται πραγματικά να περιμένει για το getFreshValue. Ο συμβιβασμός είναι ο τελευταίος χρήστης που ζητάει τα δεδομένα θα πάρει την παλιά τιμή. Νομίζω ότι αυτός είναι ένα λογικός συμβιβασμός.

Είμαι πολύ ευχαριστημένος με αυτήν την υλοποίηση και είναι πιθανό ότι τελικά θα αντιγράψω/επικολλήσω αυτό το τμήμα της ανάρτησης σε README.md για κάποιο μελλοντικό open source project😅

Βελτιστοποίηση εικόνας με το Cloudinary

Εντάξει παιδιά … Το Cloudinary είναι απίστευτο. Όλες οι εικόνες σε αυτόν τον ιστότοπο φιλοξενούνται στο cloudinary και στη συνέχεια παραδίδονται στον browser σας στο τέλειο μέγεθος και μορφή για τη συσκευή σας. Χρειάστηκε λίγη δουλειά (και πολλά χρήματα … Το Cloudinary δεν είναι φθηνό) για να συμβεί αυτό το μαγικό, αλλά εξοικονομεί έναν τόνο bandwidth για εσάς και κάνει τις εικόνες να φορτώνουν πολύ πιο γρήγορα.

Ένας από τους λόγους που χρειάστηκε τόσος χρόνος για να χτιστεί ο ιστότοπός μου στο Gatsby ήταν ότι κάθε φορά που έτρεχα την κατασκευή, το Gatsby έπρεπε να δημιουργεί όλα τα μεγέθη για όλες τις εικόνες μου. Η ομάδα του Gatsby με βοήθησε να δημιουργήσω μια επίμονη προσωρινή μνήμη, αλλά αν χρειαστεί ποτέ να καταργήσω αυτήν την προσωρινή μνήμη, θα έπρεπε να τρέξω το Netlify μερικές φορές (θα έκανε timeout) για να γεμίσω ξανά την προσωρινή μνήμη, ώστε να μπορέσω να παραδόσω ξανά τον ιστότοπό μου 😬

Με το Cloudinary, δεν έχω αυτό το πρόβλημα. Απλώς ανεβάζω τη φωτογραφία, βάζω το νέο cloudinary ID στο mdx και στη συνέχεια ο ιστότοπός μου δημιουργεί τα σωστά sizesκαι srcset για τα <img />. Επειδή το Cloudinary επιτρέπει μετασχηματισμούς στη διεύθυνση URL, μπορώ να δημιουργήσω μια εικόνα που είναι ακριβώς οι διαστάσεις που θέλω για αυτά τα props.

Επιπλέον, χρησιμοποιώ το Cloudinary για να δημιουργήσω όλες τις social εικόνες στον ιστότοπο, ώστε να είναι δυναμικές (με κείμενο/προσαρμοσμένη γραμματοσειρά και τα πάντα). Κάνω το ίδιο για τις εικόνες στο The Call Kent Podcast.

Ένα άλλο ωραίο πράγμα που κάνω, που ίσως έχετε παρατηρήσει στις αναρτήσεις, είναι πως στον server κάνω ένα αίτημα για την εικόνα banner που έχει πλάτος μόλις 100pxμε χρήση blur. Στη συνέχεια, το μετατρέπω σε μια συμβολοσειρά base64. Αυτό αποθηκεύεται στην προσωρινή μνήμη μαζί με τα άλλα metadata σχετικά με τη δημοσίευση. Στη συνέχεια, όταν κάνω server-render την ανάρτηση, κάνω render την base64 θολή εικόνα (χρησιμοποιώ επίσης backdrop-filterμε CSS για να την εξομαλύνω λίγο) και στη συνέχεια ξεθωριάζω την εικόνα πλήρους μεγέθους όταν έχει ολοκληρωθεί η φόρτωση. Είμαι πολύ χαρούμενος με αυτήν την προσέγγιση.

Το Cloudinary είναι φανταστικό και είμαι ευτυχής να πληρώσω το κόστος για αυτό που μου δίνει πίσω.

MDX Σύνταξη με mdx-bundler

Χρησιμοποιώ το MDX για να γράφω τις αναρτήσεις μου στο blog από τότε που έφυγα από το Medium. Μου αρέσει πολύ που μπορώ εύκολα να έχω διαδραστικά κομμάτια στη μέση των αναρτήσεων του ιστολογίου μου χωρίς να χρειάζεται να τα χειρίζομαι με οποιονδήποτε ειδικό τρόπο στον κώδικα του ιστότοπού μου.

Όταν έφυγα από την επιλογή MDX του Gatsby και πήγα στην on-demand μεταγλώττιση του Remix , έπρεπε να βρω έναν τρόπο να κάνω αυτήν την on-demand μεταγλώττιση. Εκείνη ακριβώς την εποχή ήταν όταν δημιουργήθηκε το xdm(ένας πολύ πιο γρήγορος και χωρίς εκτελέσεις μεταγλωττιστής MDX). Δυστυχώς, είναι απλώς ένας μεταγλωττιστής, όχι ένα πακέτο. Εάν εισάγετε components στο MDX σας, πρέπει να βεβαιωθείτε ότι αυτές οι εισαγωγές θα επιλυθούν κατά την εκτέλεση αυτού του μεταγλωττισμένου κώδικα. Αποφάσισα ότι αυτό που χρειαζόμουν δεν ήταν μόνο ένας μεταγλωττιστής. Χρειαζόμουν ένα πακέτο.

Δεν υπήρχε τέτοιο πακέτο, οπότε έφτιαξα ένα: mdx-bundler. Ξεκίνησα με τo rollup και στη συνέχεια έδωσα μια δοκιμή στο esbuild που με ικανοποίησε πολύ. Είναι πολύ γρήγορο (αν και δεν είναι αρκετά γρήγορο για να το κάνετε bunle on-demand, οπότε κρατάω στην προσωρινή μνήμη την compiled version).

Όπως θα περίμενε κανείς, έχω αρκετά ενοποιημένα plugins (remark/rehype) για να αυτοματοποιήσω κάποια πράγματα για μένα κατά τη μεταγλώττιση του mdx. Έχω ένα για αυτόματη προσθήκη query params συνεργατών για συνδέσμους amazon και egghead. Έχω ένα άλλο για τη μετατροπή ενός συνδέσμου σε ένα tweet σε μια εντελώς προσαρμοσμένη ενσωμάτωση στο twitter (πολύ γρηγορότερα από τη χρήση του twitter widget) και ένα για τη μετατροπή συνδέσμων egghead σε ενσωματώσεις βίντεο. Έχω ένα άλλο προσαρμοσμένο (δανεισμένο από ένα μυστικό πακέτο από τον Ryan Florence) για τονισμό σύνταξης με βάση το Shiki και ένα για τη βελτιστοποίηση ενσωματωμένων θολών cloudinary εικόνων.

Το Unified είναι πραγματικά ισχυρό και μου αρέσει να το χρησιμοποιώ για το περιεχόμενο που βασίζεται σε markdown.

Αλληλεπίδραση βάσης δεδομένων με το Prisma

Εντάξει φίλοι μου. Ας μιλήσουμε για το Prisma. Δεν είμαι άτομο που ασχολείται πολύ με βάσεις δεδομένων … Καθόλου. Όλα τα πράγματα στο backend είναι έξω από το εμένα. Αυτό που είναι αστείο όμως είναι ότι το Remix κάνει το backend τόσο προσιτό ώστε η περισσότερη δουλειά που κάνω τους τελευταίους μήνες ήταν backend πράγματα 😆. Όχι μόνο κάνοντας queries στην Postgres, αλλά και data migrations. Είναι πραγματικά εκπληκτικό το πόσο προσιτό το κάνει το Prisma. Ας μιλήσουμε λοιπόν για αυτά τα πράγματα.

Migrations

Με το prisma, περιγράφετε τα μοντέλα της βάσης δεδομένων σας μέσω ενός αρχείου schema.prisma. Στη συνέχεια, μπορείτε να πείτε στο Prisma να το χρησιμοποιήσει για να ενημερώσει τη βάση δεδομένων σας ώστε να αντικατοπτρίζει το σχήμα σας. Εάν χρειαστεί ποτέ να αλλάξετε το σχήμα σας, μπορείτε να εκτελέσετε το prisma migrate dev — name <descriptive-name> και το πρίσμα θα δημιουργήσει τα ερωτήματα SQL που είναι απαραίτητα για να κάνετε τις ενημερώσεις του πίνακα για τις αλλαγές του σχήματός σας.

Εάν είστε προσεκτικοί σχετικά με το πώς το κάνετε αυτό, μπορείτε να κάνετε migrations με μηδενικό downtime. Τα zero downtimes δεν είναι μοναδικά στο prisma, αλλά το prisma κάνει τη δημιουργία αυτών των μεταναστεύσεων πολύ πιο απλή για μένα, έναν τύπο που δεν έχει κάνει SQL εδώ και χρόνια και ποτέ δεν του άρεσε ούτως ή άλλως 😬 .Κατά την ανάπτυξη του ιστότοπού μου, είχα 7 migrations. Το γεγονός ότι εγώ από όλους τους ανθρώπους κατάφερα να το κάνω αυτό θα έπρεπε να είναι αρκετό για να σας πείσει 😅

Typescript

Το schema.prisma μπορεί να χρησιμοποιηθεί επίσης για να παράξει types για την βάση σας και εδώ είναι που τα πράγματα γίνονται τέλεια. Ακολουθεί ένα παράδειγμα ενός query:

// Typescriptconst users = await prisma.user.findMany({
select: {
id: true,
email: true,
firstName: true,
},
})
//Typescript// Αυτός είναι τύπος users. Για να είμαι ξεκάθαρος, δεν χρείαζεται να το γράψω εγώ αυτό,
// η κλήση απο πάνω επιστρέφει τον σωστό τύπο αυτόματα:
const users: Array<{
id: string
email: string
firstName: string
}>

Και αν ήθελα να πάρω και το team :

//Typescriptconst users = await prisma.user.findMany({
select: {
id: true,
email: true,
firstName: true,
team: true, // <-- απλά προσθέστε αυτό
},
})

Και τώρα ξαφνικά ο πίνακας users είναι:

//Typescriptconst users: Array<{
id: string
email: string
firstName: string
team: Team
}>

Και αν ήθελα να πάρω και όλα τα posts που έχει διαβάσει ο user; Θα χρειαζόμουν κάποιον μαγικό graphql resolver; Όχι! Δείτε αυτό:

// Typescriptconst users = await prismaRead.user.findMany({
select: {
id: true,
email: true,
firstName: true,
team: true,
postReads: {
select: {
postSlug: true,
},
},
},
})

Και τώρα ο πίνακας των users μου είναι:

// Typescriptconst users: Array<{
firstName: string
email: string
id: string
team: Team
postReads: Array<{
postSlug: string
}>
}>

Και με το Remix μπορώ εύκολα να βάλω queries στον loader και μετά να έχω typed δεδομένα στα components μου:

//Typescripttype LoaderData = Await<ReturnType<typeof getLoaderData>>async function getLoaderData() {
const users = await prismaRead.user.findMany({
select: {
id: true,
email: true,
firstName: true,
team: true,
postReads: {
select: {
postSlug: true,
},
},
},
})
return {users}
}
export const loader: LoaderFunction = async ({request}) => {
return json(await getLoaderData())
}
export default function UsersPage() {
const data = useLoaderData<LoaderData>()
return (
<div>
<h1>Users</h1>
<ul>
{/* all this auto-completes and type checks!! */}
{data.users.map(user => (
<li key={user.id}>
<div>{user.firstName}</div>
</li>
))}
</ul>
</div>
)
}

Το Prisma με έχει κάνει, έναν frontend developer, να νιώθω άνετα να δουλεύω με βάσεις

Έλεγχος ταυτότητας με Magic Links

Λίγο καιρό πριν έκανα ένα tweet:

Kent C. Dodds tweet

Ναι…Έφτιαξα το δικό μου authentication σε αυτόν τον ιστότοπο. Είχα όμως έναν καλό λόγο! Θυμάστε όλη την παραπάνω συζήτηση σχετικά με το να κάνετε τα πράγματα πολύ γρήγορα, συσπειρώνοντας τους node servers και τις βάσεις δεδομένων κοντά στους χρήστες; Λοιπόν, θα αναιρούσα όλη αυτή τη δουλειά εάν χρησιμοποιούσα μια υπηρεσία authentication. Κάθε request θα έπρεπε να μεταβεί στην περιοχή που υποστηρίζει ο πάροχος για να επαληθεύσει την κατάσταση σύνδεσης του χρήστη. Πόσο απογοητευτικό θα ήταν αυτό;

Την εποχή που δούλευα σε αυτό το πρόβλμα, ο Ryan Florence έκανε μερικά live streams όπου εφάρμοσε δικό του authentication για τη δική του εφαρμογή Remix. Δεν φαινόταν τόσο περίπλοκο. Και ήταν αρκετά ευγενικός για να μου δώσει μια περιγραφή των απαιτούμενων πραγμάτων και τα περισσότερα τα έκανα σε μία μόνο μέρα!

Κάτι που βοήθησε πολύ ήταν η χρήση μαγικών συνδέσμων για έλεγχο ταυτότητας. Κάνοντας αυτό σημαίνει ότι δεν χρειάζεται να ανησυχώ για την αποθήκευση κωδικών πρόσβασης ή τον χειρισμό επαναφοράς/αλλαγών κωδικού πρόσβασης. Αλλά αυτό δεν ήταν μόνο μια εγωιστική/τεμπέλικη απόφαση. Νιώθω έντονα ότι οι μαγικοί σύνδεσμοι είναι το καλύτερο σύστημα ελέγχου ταυτότητας για μια εφαρμογή όπως η δική μου. Λάβετε υπόψη ότι σχεδόν κάθε άλλη εφαρμογή διαθέτει ένα σύστημα εγγραφής παρόμοιο με “μαγικό σύνδεσμο”, ακόμη και αν είναι λόγω της “επαναφοράς κωδικού πρόσβασης” που σας στέλνει ένα μήνυμα ηλεκτρονικού ταχυδρομείου για να επαναφέρετε τον κωδικό πρόσβασής σας. Οπότε σίγουρα δεν είναι λιγότερο ασφαλές. Στην πραγματικότητα είναι πιο ασφαλής, επειδή δεν υπάρχει κωδικός πρόσβασης για να χάσετε.

Α, και πριν πείτε:

Αλλά εάν δεν υπάρχει κωδικός πρόσβασης, δεν μπορώ να χρησιμοποιήσω τον διαχειριστή κωδικών πρόσβασης και θα ξεχάσω ποιες από τις 30 διευθύνσεις ηλεκτρονικού ταχυδρομείου μου χρησιμοποίησα για να εγγραφώ στον ιστότοπό σας!

Ο διαχειριστής κωδικών πρόσβασής σας μπορεί σίγουρα να αποθηκεύσει πληροφορίες σύνδεσης που έχουν μόνο μια διεύθυνση ηλεκτρονικού ταχυδρομείου και κανένα κωδικό πρόσβασης. Κάνε αυτό.

Εντάξει, ας ρίξουμε μια ματιά σε ένα διάγραμμα της ροής ελέγχου ταυτότητας:

Authentication FLow

Μερικά πράγματα που θέλω να επισημάνω με αυτήν τη ροή είναι ότι δεν υπάρχει αλληλεπίδραση με τη βάση δεδομένων μέχρι να εγγραφεί ο χρήστης. Επίσης, η ροή για εγγραφή και σύνδεση είναι η ίδια. Αυτό απλοποιεί τα πράγματα αρκετά για μένα.

Τώρα, ας ρίξουμε μια ματιά στο τι συμβαίνει όταν ένας χρήστης μεταβαίνει σε μια authenticated σελίδα.

Session Flow

Τα βασικά είναι πολύ απλά:

  • Πάρτε το αναγνωριστικό περιόδου σύνδεσης από το cookie
  • Πάρτε το αναγνωριστικό χρήστη από το session
  • Πάρτε τον χρήστη
  • Ενημερώστε τον χρόνο λήξης, έτσι ώστε οι ενεργοί χρήστες σπάνια να χρειάζονται εκ νέου έλεγχο ταυτότητας
  • Εάν κάποιο από αυτά αποτύχει, κάντε εκκαθάριση και ανακατεύθυνση

Ειλικρινά δεν είναι τόσο περίπλοκο όσο το θυμόμουν όταν έκανα έλεγχο ταυτότητας πριν από χρόνια σε άλλες εφαρμογές που δούλευα. Το Remix βοηθά να γίνει πολύ πιο εύκολο με το cookie abstraction.

Remix

Εντάξει παιδιά. Από όλα τα εργαλεία που χρησιμοποιώ, το Remix έχει τον μεγαλύτερο αντίκτυπο στην παραγωγικότητά μου και την απόδοση του ιστότοπού μου. Το Remix μου δίνει τη δυνατότητα να κάνω όλα αυτά τα υπέροχα πράγματα χωρίς να περιπλέκω υπερβολικά τη βάση δεδομένων μου.

Σίγουρα θα γράψω πολλές αναρτήσεις για το Remix στο μέλλον, οπότε εγγραφείτε για να τις παρακολουθείσετε. Αλλά εδώ είναι μια γρήγορη λίστα με τους λόγους για τους οποίους το Remix ήταν τόσο φανταστικό για μένα:

  1. Η ευκολία επικοινωνίας μεταξύ server και client. Η υπερφόρτωση δεδομένων δεν αποτελεί πλέον πρόβλημα γιατί μου είναι τόσο εύκολο να φιλτράρω αυτό που θέλω στον κώδικα του server και να έχω ακριβώς αυτό που χρειάζομαι στον κωδικό του client. Εξαιτίας αυτού, δεν υπάρχει ανάγκη για ένα τεράστιο και περίπλοκο graphql backend και βιβλιοθήκη πελάτη για να αντιμετωπίσει αυτό το ζήτημα (μπορείτε σίγουρα να χρησιμοποιήσετε graphql με remix αν θέλετε). Θα γράψω πολλές αναρτήσεις στο blog για αυτό τους επόμενους μήνες.
  2. Η αυτόματη απόδοση που λαμβάνω από τη χρήση της διαδικτυακής πλατφόρμας από την Remix. Αυτό είναι επίσης μεγάλο πράγμα που θα απαιτήσει πολλαπλές αναρτήσεις για να εξηγηθεί.
  3. Η δυνατότητα να έχει CSS για μια συγκεκριμένη διαδρομή και να ξέρω ότι δεν θα συγκρουστώ με το CSS σε καμία άλλη διαδρομή. 👋 αντίο CSS-in-JS.
  4. Το γεγονός ότι δεν χρειάζεται καν να σκέφτομαι μια server cache επειδή το Remix το χειρίζεται όλο αυτό για μένα (συμπεριλαμβανομένων μετά από mutations). Όλα τα components μου μπορούν να υποθέσουν ότι τα δεδομένα είναι έτοιμα. Η διαχείριση εξαιρέσεων/σφαλμάτων είναι δηλωτική. Και το Remix δεν εφαρμόζει τη δική του προσωρινή μνήμη, αλλά αντ ‘αυτού αξιοποιεί την προσωρινή μνήμη του προγράμματος περιήγησης για να κάνει τα πράγματα εξαιρετικά γρήγορα ακόμη και μετά από μια επαναφόρτωση (ή ανοίγοντας έναν σύνδεσμο σε μια νέα καρτέλα).
  5. Δεν χρειάζεται να ανησυχώ για ένα Layout component όπως με άλλα frameworks και τα οφέλη που μου προσφέρει από την άποψη της φόρτωσης δεδομένων. Και πάλι, αυτό θα απαιτήσει μια δημοσίευση ιστολογίου.

Αναφέρω ότι πολλά από αυτά θα απαιτήσουν μια δημοσίευση ιστολογίου. Όχι επειδή χρειάζεται να μάθετε πράγματα για να επωφεληθείτε από αυτά, αλλά για να σας εξηγήσω ότι δεν χρειάζεται να τα μάθετε. Είναι ακριβώς ο τρόπος που λειτουργεί το Remix. Ξοδεύω λιγότερο χρόνο για να σκεφτώ πώς να κάνω τα πράγματα να λειτουργούν και περισσότερο χρόνο συνειδητοποιώντας ότι οι δυνατότητες της εφαρμογής μου δεν περιορίζονται από framework, αλλά από τη «φαντασία» μου.

Συμπέρασμα

Δεν μπορώ να σας πω πόσα έχω μάθει από τη δημιουργία αυτής της ιστοσελίδας. Ήταν πολύ διασκεδαστικό και είμαι ενθουσιασμένος που βάζω τις γνώσεις μου σε αναρτήσεις και εργαστήρια για να σας διδάξω τις ιδιαιτερότητες του πώς έκανα αυτά τα πράγματα, ώστε να μπορείτε να το κάνετε κι εσείς. Στην πραγματικότητα, έχω ήδη προγραμματίσει κάποια εργαστήρια για να παρακολουθήσετε! Πάρτε εισιτήρια τώρα. Ανυπομονώ να σας δω εκεί! Φροντίστε και συνεχίστε την καλή δουλειά.

Και μην ξεχνάτε, εάν δεν έχετε διαβάσει ήδη την ανάρτηση “Introducing the new kentcdodds.com”, παρακαλώ διαβάστε τη!

--

--

Senior Frontend Engineer

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store