Εκατομμύρια εφαρμογές front-end διαχειρίζονται εκδόσεις ειδικά για το περιβάλλον. Για κάθε περιβάλλον —είτε πρόκειται για ανάπτυξη, σκηνοθεσία ή παραγωγή— πρέπει να δημιουργηθεί μια ξεχωριστή έκδοση της εφαρμογής frontend και να ρυθμιστούν οι κατάλληλες μεταβλητές περιβάλλοντος. Ο αριθμός των εκδόσεων πολλαπλασιάζεται εάν εμπλέκονται πολλές εφαρμογές, αυξάνοντας την απογοήτευση. Αυτό ήταν ένα κοινό πρόβλημα εδώ και πολύ καιρό, αλλά υπάρχει καλύτερος τρόπος χειρισμού μεταβλητών περιβάλλοντος. Βρήκα έναν τρόπο να απλοποιήσω αυτήν τη διαδικασία και σε αυτό το άρθρο θα σας καθοδηγήσω βήμα προς βήμα για να δημιουργήσετε μια αποτελεσματική διαδικασία που θα μειώσει τους χρόνους κατασκευής και θα σας βοηθήσει να διασφαλίσετε τη συνέπεια μεταξύ των περιβαλλόντων στα έργα σας. Κατανόηση μεταβλητών περιβάλλοντος Πριν ξεκινήσουμε, νομίζω ότι πρέπει να κάνουμε μια ανακεφαλαίωση. Οι εφαρμογές Ιστού βασίζονται σχεδόν πάντα σε μεταβλητές γνωστές ως ", οι οποίες συχνά περιλαμβάνουν εσωτερικά τελικά σημεία συστήματος, συστήματα ενοποίησης, κλειδιά συστήματος πληρωμών, αριθμούς έκδοσης κ.λπ. Φυσικά, οι τιμές αυτών των μεταβλητών διαφέρουν ανάλογα με το περιβάλλον στο οποίο αναπτύσσεται η εφαρμογή. "μεταβλητές περιβάλλοντος Για παράδειγμα, φανταστείτε μια εφαρμογή που αλληλεπιδρά με μια πύλη πληρωμής. Στο περιβάλλον ανάπτυξης, η διεύθυνση URL της πύλης πληρωμής μπορεί να οδηγεί σε ένα sandbox για δοκιμή (https://sandbox.paymentgateway.com), ενώ στο περιβάλλον παραγωγής, οδηγεί στη ζωντανή υπηρεσία (https://live.paymentgateway.com ). Ομοίως, διαφορετικά κλειδιά API ή οποιαδήποτε άλλη συγκεκριμένη ρύθμιση περιβάλλοντος χρησιμοποιούνται για κάθε περιβάλλον για να διασφαλιστεί η ασφάλεια των δεδομένων και να αποφευχθεί η ανάμειξη των περιβαλλόντων. Προκλήσεις στην ανάπτυξη frontend Κατά τη δημιουργία , αυτό δεν αποτελεί πρόβλημα. Η δήλωση αυτών των μεταβλητών στον κώδικα της εφαρμογής είναι αρκετή, καθώς οι τιμές αυτών των μεταβλητών αποθηκεύονται στο περιβάλλον διακομιστή όπου αναπτύσσεται το backend. Με αυτόν τον τρόπο, η εφαρμογή υποστήριξης αποκτά πρόσβαση σε αυτά κατά την εκκίνηση. εφαρμογών υποστήριξης Ωστόσο, με τα πράγματα γίνονται κάπως πιο περίπλοκα. Δεδομένου ότι εκτελούνται στο πρόγραμμα περιήγησης του χρήστη, δεν έχουν πρόσβαση σε συγκεκριμένες τιμές μεταβλητών περιβάλλοντος. Για να αντιμετωπιστεί αυτό, οι τιμές αυτών των μεταβλητών τυπικά "ψήνονται" στην εφαρμογή frontend κατά το χρόνο δημιουργίας. Με αυτόν τον τρόπο, όταν η εφαρμογή εκτελείται στο πρόγραμμα περιήγησης του χρήστη, όλες οι απαραίτητες τιμές είναι ήδη ενσωματωμένες στην εφαρμογή frontend. τις εφαρμογές frontend Αυτή η προσέγγιση, όπως πολλές άλλες, συνοδεύεται από μια προειδοποίηση: πρέπει να δημιουργήσετε μια ξεχωριστή έκδοση της ίδιας εφαρμογής frontend για κάθε περιβάλλον, έτσι ώστε κάθε έκδοση να περιέχει τις αντίστοιχες τιμές της. , ας υποθέσουμε ότι έχουμε τρία περιβάλλοντα: Για παράδειγμα Ανάπτυξη για εσωτερικές δοκιμές· στάδιο για τη δοκιμή ολοκλήρωσης· και παραγωγή για τους πελάτες. Για να υποβάλετε την εργασία σας για δοκιμή, δημιουργείτε την εφαρμογή και την αναπτύσσετε στο περιβάλλον ανάπτυξης. Μετά την ολοκλήρωση των εσωτερικών δοκιμών, πρέπει να δημιουργήσετε ξανά την εφαρμογή για να την αναπτύξετε στη σκηνή και, στη συνέχεια, να την δημιουργήσετε ξανά για ανάπτυξη στην παραγωγή. Εάν το έργο περιέχει περισσότερες από μία εφαρμογές front-end, ο αριθμός τέτοιων εκδόσεων αυξάνεται σημαντικά. Επιπλέον, μεταξύ αυτών των εκδόσεων, η βάση κώδικα δεν αλλάζει — η δεύτερη και η τρίτη έκδοση βασίζονται στον ίδιο πηγαίο κώδικα. Όλα αυτά καθιστούν τη διαδικασία απελευθέρωσης ογκώδη, αργή και δαπανηρή, καθώς και κίνδυνο διασφάλισης ποιότητας. Ίσως το build να ήταν καλά δοκιμασμένο στο περιβάλλον ανάπτυξης, αλλά το στάδιο κατασκευής είναι τεχνικά νέο, πράγμα που σημαίνει ότι πλέον υπάρχει νέα πιθανότητα λάθους. Έχετε δύο εφαρμογές με χρόνους κατασκευής X και Y δευτερόλεπτα. Για αυτά τα τρία περιβάλλοντα, και οι δύο εφαρμογές θα χρειάζονταν σε χρόνο κατασκευής. Ωστόσο, εάν μπορούσατε να δημιουργήσετε κάθε εφαρμογή μόνο μία φορά και να χρησιμοποιήσετε την ίδια έκδοση σε όλα τα περιβάλλοντα, ο συνολικός χρόνος θα μειωνόταν σε μόλις δευτερόλεπτα, τριπλασιάζοντας τον χρόνο κατασκευής. Ένα παράδειγμα: 3X + 3Y X + Y Αυτό μπορεί να κάνει μεγάλη διαφορά στους αγωγούς frontend, όπου οι πόροι είναι περιορισμένοι και οι χρόνοι κατασκευής μπορεί να κυμαίνονται από λίγα λεπτά έως πολύ περισσότερο από μία ώρα. Το ζήτημα είναι παρόν σχεδόν σε κάθε εφαρμογή διεπαφής παγκοσμίως και συχνά δεν υπάρχει τρόπος επίλυσής του. Ωστόσο, αυτό είναι ένα σοβαρό πρόβλημα, ειδικά από επιχειρηματική σκοπιά. Δεν θα ήταν υπέροχο αν αντί να δημιουργήσετε τρεις ξεχωριστές εκδόσεις, μπορούσατε απλώς να δημιουργήσετε ένα και να το αναπτύξετε σε όλα τα περιβάλλοντα; Λοιπόν, βρήκα έναν τρόπο να κάνω ακριβώς αυτό. Βελτιστοποίηση αναπτύξεων frontend. Το εγχειρίδιο Ρύθμιση μεταβλητών περιβάλλοντος Αρχικά, πρέπει να δημιουργήσετε ένα αρχείο στο αποθετήριο του έργου σας στο frontend όπου θα παρατίθενται οι απαιτούμενες μεταβλητές περιβάλλοντος. Αυτά θα χρησιμοποιηθούν από τον προγραμματιστή τοπικά. Συνήθως, αυτό το αρχείο ονομάζεται , το οποίο μπορούν να διαβάσουν τα περισσότερα σύγχρονα πλαίσια frontend. Ακολουθεί ένα παράδειγμα τέτοιου αρχείου: .env.local CLIENT_ID='frontend-development' API_URL=/api/v1' PUBLIC_URL='/' COMMIT_SHA='' Σημείωση: διαφορετικά πλαίσια απαιτούν διαφορετικές συμβάσεις ονομασίας για μεταβλητές περιβάλλοντος. Για παράδειγμα, στο React, πρέπει να προσαρτήσετε στα ονόματα των μεταβλητών. Αυτό το αρχείο δεν χρειάζεται απαραίτητα να περιλαμβάνει μεταβλητές που επηρεάζουν άμεσα την εφαρμογή. μπορεί επίσης να περιέχει χρήσιμες πληροφορίες εντοπισμού σφαλμάτων. Πρόσθεσα τη μεταβλητή , την οποία αργότερα θα τραβήξουμε από την εργασία κατασκευής για να παρακολουθήσουμε τη δέσμευση στην οποία βασίστηκε αυτή η κατασκευή. REACT_APP_ COMMIT_SHA Στη συνέχεια, δημιουργήστε ένα αρχείο που ονομάζεται , όπου μπορείτε να ορίσετε ποιες μεταβλητές περιβάλλοντος χρειάζεστε. Το πλαίσιο frontend θα τα εγχύσει για εσάς. Για το React, για παράδειγμα, αποθηκεύονται στο αντικείμενο : environment.js process.env const ORIGIN_ENVIRONMENTS = window.ORIGIN_ENVIRONMENTS = { CLIENT_ID: process.env.CLIENT_ID, API_URL: process.env.API_URL, PUBLIC_URL: process.env.PUBLIC_URL, COMMIT_SHA: process.env.COMMIT_SHA }; export const ENVIRONMENT = { clientId: ORIGIN_ENVIRONMENTS.CLIENT_ID, apiUrl: ORIGIN_ENVIRONMENTS.API_URL, publicUrl: ORIGIN_ENVIRONMENTS.PUBLIC_URL ?? "/", commitSha: ORIGIN_ENVIRONMENTS.COMMIT_SHA, }; Εδώ, ανακτάτε όλες τις αρχικές τιμές για τις μεταβλητές στο αντικείμενο , το οποίο σας επιτρέπει να τις προβάλλετε στην κονσόλα του προγράμματος περιήγησης. Επιπλέον, πρέπει να τα αντιγράψετε στο αντικείμενο , όπου μπορείτε επίσης να ορίσετε ορισμένες προεπιλογές, για παράδειγμα: υποθέτουμε ότι είναι / από προεπιλογή. Χρησιμοποιήστε το αντικείμενο όπου χρειάζονται αυτές οι μεταβλητές στην εφαρμογή. window.ORIGIN_ENVIRONMENTS ENVIRONMENT publicUrl ENVIRONMENT Σε αυτό το στάδιο, έχετε εκπληρώσει όλες τις ανάγκες για τοπική ανάπτυξη. Αλλά ο στόχος είναι να χειριστείτε διαφορετικά περιβάλλοντα. Για να το κάνετε αυτό, δημιουργήστε ένα αρχείο με το ακόλουθο περιεχόμενο: .env CLIENT_ID='<client_id>' API_URL='<api_url>' PUBLIC_URL='<public_url>' COMMIT_SHA=$COMMIT_SHA Σε αυτό το αρχείο, θα θελήσετε να καθορίσετε σύμβολα κράτησης θέσης για τις μεταβλητές που εξαρτώνται από το περιβάλλον. Μπορούν να είναι οτιδήποτε θέλετε, αρκεί να είναι μοναδικά και να μην επικαλύπτονται με κανέναν τρόπο με τον πηγαίο κώδικα σας. Για επιπλέον σιγουριά, μπορείτε ακόμη και να χρησιμοποιήσετε UUID – Καθολικά μοναδικά αναγνωριστικά. Για αυτές τις μεταβλητές που δεν αλλάζουν σε περιβάλλοντα (π.χ. το commit hash), μπορείτε είτε να γράψετε τις πραγματικές τιμές απευθείας είτε να χρησιμοποιήσετε τιμές που θα είναι διαθέσιμες κατά την εργασία δημιουργίας (όπως ). Το πλαίσιο διεπαφής θα αντικαταστήσει αυτά τα σύμβολα κράτησης θέσης με πραγματικές τιμές κατά τη διαδικασία δημιουργίας: $COMMIT_SHA Αρχείο Τώρα έχετε την ευκαιρία να βάλετε πραγματικές αξίες αντί των placeholders. Για να το κάνετε αυτό, δημιουργήστε ένα αρχείο, (Επέλεξα Python, αλλά μπορείτε να χρησιμοποιήσετε οποιοδήποτε εργαλείο για αυτόν τον σκοπό), το οποίο θα πρέπει πρώτα να περιέχει μια αντιστοίχιση των θέσεων κράτησης θέσης σε ονόματα μεταβλητών: inject.py replacement_map = { "<client_id>": "CLIENT_ID", "<api_url>": "API_URL", "<public_url>": "PUBLIC_URL", "%3Cpublic_url%3E": "PUBLIC_URL" } Σημειώστε ότι παρατίθεται δύο φορές και η δεύτερη καταχώρηση έχει διαφύγει αγκύλες. Το χρειάζεστε για όλες τις μεταβλητές που χρησιμοποιούνται σε αρχεία CSS και HTML. public_url Τώρα ας προσθέσουμε μια λίστα αρχείων που θέλουμε να τροποποιήσουμε (αυτό θα ήταν ένα παράδειγμα για το Nginx): base_path = 'usr/share/nginx/html' target_files = [ f'{base_path}/static/js/main.*.js', f'{base_path}/static/js/chunk.*.js', f'{base_path}/static/css/main.*.css', f'{base_path}/static/css/chunk.*.css', f'{base_path}/index.html' ] Στη συνέχεια, δημιουργούμε το αρχείο , όπου θα λάβουμε την αντιστοίχιση και τη λίστα των αρχείων τεχνουργημάτων κατασκευής (όπως αρχεία JS, HTML και CSS) και θα αντικαταστήσουμε τα σύμβολα κράτησης θέσης με τις τιμές των μεταβλητών από το τρέχον περιβάλλον μας: injector.py import os import glob def inject_envs(filename, replacement_map): with open(filename) as r: lines = r.read() for key, value in replacement_map.items(): lines = lines.replace(key, os.environ.get(value) or '') with open(filename, "w") as w: w.write(lines) def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) Και στη συνέχεια, στο αρχείο , προσθέστε αυτήν τη γραμμή (μην ξεχάσετε να εισαγάγετε ): inject.py injector.py injector.inject(target_files, replacement_map, base_path) Τώρα πρέπει να βεβαιωθούμε ότι το σενάριο εκτελείται μόνο κατά την ανάπτυξη. Μπορείτε να το προσθέσετε στο στην εντολή αφού εγκαταστήσετε την Python και αντιγράψετε όλα τα τεχνουργήματα: inject.py Dockerfile CMD RUN apk add python3 COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/ci /ci COPY --from=build /app/build /usr/share/nginx/html CMD ["/bin/sh", "-c", "python3 ./ci/inject.py && nginx -g 'daemon off;'"]That's it! This way, during each deployment, the pre-built files will be used, with variables specific to the deployment environment injected into them. Αυτό είναι όλο! Με αυτόν τον τρόπο, κατά τη διάρκεια κάθε ανάπτυξης, θα χρησιμοποιούνται τα προκατασκευασμένα αρχεία, με μεταβλητές συγκεκριμένες για το περιβάλλον ανάπτυξης να εισάγονται σε αυτά. Αρχείο: Χειρισμός κατακερματισμού ονομάτων αρχείου για σωστή αποθήκευση στην προσωρινή μνήμη του προγράμματος περιήγησης Ένα πράγμα - εάν τα τεχνουργήματα κατασκευής σας περιλαμβάνουν κατακερματισμό περιεχομένου στα ονόματα αρχείων τους, αυτή η ένεση δεν θα επηρεάσει τα ονόματα αρχείων και αυτό θα μπορούσε να προκαλέσει προβλήματα με την προσωρινή αποθήκευση του προγράμματος περιήγησης. Για να το διορθώσετε αυτό, αφού τροποποιήσετε τα αρχεία με μεταβλητές που έχουν εισαχθεί, θα χρειαστεί να: Δημιουργήστε ένα νέο κατακερματισμό για τα ενημερωμένα αρχεία. Προσθέστε αυτό το νέο κατακερματισμό στα ονόματα αρχείων, ώστε το πρόγραμμα περιήγησης να τα αντιμετωπίζει ως νέα αρχεία. Ενημερώστε τυχόν αναφορές στα παλιά ονόματα αρχείων στον κώδικά σας (όπως δηλώσεις εισαγωγής) για να ταιριάζουν με τα νέα ονόματα αρχείων. Για να το εφαρμόσετε, προσθέστε μια εισαγωγή βιβλιοθήκης κατακερματισμού ( ) και τις ακόλουθες συναρτήσεις στο αρχείο . import hashlib inject.py def sha256sum(filename): h = hashlib.sha256() b = bytearray(128 * 1024) mv = memoryview(b) with open(filename, 'rb', buffering=0) as f: while n := f.readinto(mv): h.update(mv[:n]) return h.hexdigest() def replace_filename_imports(filename, new_filename, base_path): allowed_extensions = ('.html', '.js', '.css') for path, dirc, files in os.walk(base_path): for name in files: current_filename = os.path.join(path, name) if current_filename.endswith(allowed_extensions): with open(current_filename) as f: s = f.read() s = s.replace(filename, new_filename) with open(current_filename, "w") as f: f.write(s) def rename_file(fullfilename): dirname = os.path.dirname(fullfilename) filename, ext = os.path.splitext(os.path.basename(fullfilename)) digest = sha256sum(fullfilename) new_filename = f'{filename}.{digest[:8]}' new_fullfilename = f'{dirname}/{new_filename}{ext}' os.rename(fullfilename, new_fullfilename) return filename, new_filename Ωστόσο, δεν χρειάζεται να μετονομαστούν όλα τα αρχεία. Για παράδειγμα, το όνομα αρχείου πρέπει να παραμείνει αμετάβλητο και για να το πετύχετε αυτό, δημιουργήστε μια κλάση που θα αποθηκεύει μια σημαία που θα υποδεικνύει εάν είναι απαραίτητη η μετονομασία: index.html TargetFile class TargetFile: def __init__(self, glob, should_be_renamed = True): self.glob = glob self.should_be_renamed = should_be_renamed Τώρα πρέπει απλώς να αντικαταστήσετε τη συστοιχία διαδρομών αρχείων στο με μια σειρά αντικειμένων κλάσης : inject.py TargetFile target_files = [ injector.TargetFile(f'{base_path}/static/js/main.*.js'), injector.TargetFile(f'{base_path}/static/js/chunk.*.js'), injector.TargetFile(f'{base_path}/static/css/main.*.css'), injector.TargetFile(f'{base_path}/static/css/chunk.*.css'), injector.TargetFile(f'{base_path}/index.html', False) ] Και ενημερώστε τη συνάρτηση στο ώστε να συμπεριλάβει τη μετονομασία του αρχείου εάν έχει οριστεί η σημαία: inject injector.py def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) if target_file.should_be_renamed: filename, new_filename = rename_file(filename) replace_filename_imports(filename, new_filename, base_path) Ως αποτέλεσμα, τα αρχεία τεχνουργημάτων θα ακολουθούν αυτήν τη μορφή ονομασίας: . . . <origin-file-name> <injection-hash> <extension> Όνομα αρχείου πριν από την ένεση: Όνομα αρχείου μετά την ένεση: Οι ίδιες μεταβλητές περιβάλλοντος αποδίδουν το ίδιο όνομα αρχείου, επιτρέποντας στο πρόγραμμα περιήγησης του χρήστη να αποθηκεύσει σωστά το αρχείο προσωρινά. Υπάρχει πλέον εγγύηση ότι οι σωστές τιμές αυτών των μεταβλητών θα αποθηκευτούν στην κρυφή μνήμη του προγράμματος περιήγησης, ως αποτέλεσμα – καλύτερη απόδοση για τον πελάτη. Μια λύση για βελτιωμένες αναπτύξεις Η παραδοσιακή προσέγγιση της δημιουργίας ξεχωριστών κατασκευών για κάθε περιβάλλον έχει οδηγήσει σε μερικές κρίσιμες αναποτελεσματικότητα, που μπορεί να είναι ένα ζήτημα για ομάδες με περιορισμένους πόρους. Τώρα έχετε ένα σχέδιο για μια διαδικασία έκδοσης που μπορεί να επιλύσει παρατεταμένους χρόνους ανάπτυξης, υπερβολικές εκδόσεις και αυξημένους κινδύνους όσον αφορά τη διασφάλιση ποιότητας για εφαρμογές frontend – όλα αυτά. Όλα αυτά εισάγοντας ένα νέο επίπεδο εγγυημένης συνέπειας σε όλα τα περιβάλλοντα. Αντί να χρειάζεστε N builds, θα χρειαστείτε μόνο ένα. Για την επερχόμενη έκδοση, μπορείτε απλώς να αναπτύξετε την έκδοση που έχει ήδη δοκιμαστεί, η οποία βοηθά επίσης στην επίλυση πιθανών προβλημάτων σφαλμάτων, καθώς η ίδια έκδοση θα χρησιμοποιηθεί σε όλα τα περιβάλλοντα. Επιπλέον, η ταχύτητα εκτέλεσης αυτού του σεναρίου είναι ασύγκριτα ταχύτερη ακόμη και από την πιο βελτιστοποιημένη έκδοση. Για παράδειγμα, τα τοπικά σημεία αναφοράς σε ένα MacBook 14 PRO, M1, 32 GB είναι τα εξής: Η προσέγγισή μου απλοποιεί τη διαδικασία έκδοσης, διατηρεί την απόδοση της εφαρμογής επιτρέποντας αποτελεσματικές στρατηγικές προσωρινής αποθήκευσης και διασφαλίζει ότι τα σφάλματα που σχετίζονται με την έκδοση δεν θα εισέλθουν στα περιβάλλοντα. Επιπλέον, όλος ο χρόνος και η προσπάθεια που ξοδεύονταν προηγουμένως σε κουραστικές εργασίες κατασκευής μπορούν τώρα να επικεντρωθούν στη δημιουργία μιας ακόμα καλύτερης εμπειρίας χρήστη. Τι να μην αγαπάς; Διασφαλίζουμε ότι τα σφάλματα που σχετίζονται με την κατασκευή δεν γλιστρούν στην εφαρμογή για άλλα περιβάλλοντα. Μπορεί να υπάρχουν σφάλματα φαντασμάτων που εμφανίζονται λόγω ατελειών στα συστήματα κατασκευής. Οι πιθανότητες είναι ελάχιστες, αλλά υπάρχουν.