AngularJS: 'πρότυπο' εναντίον `templateUrl`

Τους τελευταίους μήνες, έχω εξετάσει διάφορους τρόπους για να βελτιώσω τις επιδόσεις εκτέλεσης στο γιγαντιαίο SPA στο οποίο εργάζομαι στο Domo. Έχουμε σημειώσει κάποια σοβαρή πρόοδο, αλλά με ένα εκατομμύριο γραμμές κώδικα σε ένα SPA, μερικές από τις αλλαγές δεν είναι πάντα εύκολο. Ένα από τα μέλη της ομάδας μας έκανε την ανακάλυψη για να βοηθήσει να προσθέσει lazyloading σε έργα AngularJS, και έχουμε επενδύσει σε μεγάλο βαθμό σε αυτό. Λίγα διαφορετικά μέλη της ομάδας (Jason και Tim) έσπευσαν να μας βοηθήσουν να μετρήσουμε το χρόνο που χρειάζεται η εφαρμογή μας για να προετοιμαστεί πλήρως. Χρησιμοποιήσαμε επίσης webpack για τη βελτιστοποίηση της κατασκευής, αλλά και για την αλλαγή ορισμένων μοντέλων που χρησιμοποιούμε. Όταν συνδυάσαμε το webpack με το ocLazyload, βρήκαμε μια σοβαρή νίκη για τα προγράμματα AngularJS.

Αυτή την περασμένη εβδομάδα, ανέλαβα το καθήκον να αλλάξω όλες τις δηλώσεις προτύπου συστατικού / οδηγίας και να τις αλλάξω από το templateUrl στο πρότυπο. Αντί να μετακινήσετε με μη αυτόματο τρόπο όλα τα πρότυπα από τα ξεχωριστά αρχεία .html στα αντίστοιχα αρχεία JS, αποφασίσαμε να χρησιμοποιήσουμε έναν φορτωτή webpack και να απαιτήσουμε τα πρότυπα ως inline strings. Για να το εξηγήσω καλύτερα ... επιτρέψτε μου να σας δείξω τι εννοώ. Το παρακάτω είναι ένα δείγμα του στοιχείου AngularJS:

Όπως μπορείτε να δείτε, στο πρώτο παράδειγμα, υπάρχει ένα στοιχείο που χρησιμοποιεί ένα templateUrl για να φορτώσει το πρότυπο του. Αυτό είναι προβληματικό στην καλύτερη περίπτωση, IMO. Αυτό σημαίνει ότι θα χρειαστεί είτε να αναπτύξετε το αρχείο foo / bar / myComponent.html στην παραγωγή έτσι ώστε η εφαρμογή παραγωγής σας να μπορέσει να φορτώσει το κομμάτι του προτύπου μέσω ενός δεύτερου αιτήματος δικτύου για να το πάρει, Ή σημαίνει ότι θα χρειαστεί να προσθέσετε ένα build βήμα που θα βρει όλες τις εμφανίσεις του templateUrl και θα φέρει τα πρότυπα αυτά στο πρότυπο Cache template. Και οι δύο αυτές λύσεις έχουν προβλήματα.

Τα προβλήματα με το πρώτο είναι προφανή: αν όλα τα πρότυπα παραγωγής σας απαιτούσαν ξεχωριστό αίτημα δικτύου για να τα λάβουν, τότε η φόρτωση οποιασδήποτε προβολής θα απαιτούσε αιτήσεις δικτύου N για να πάρει όλες τις προβολές, όπου N είναι ο αριθμός των στοιχείων / οδηγίες / ngIncludes κατά την άποψή σας.

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

Λαμβάνοντας υπόψη τις πολλές εκατοντάδες και εκατοντάδες πρότυπα που έχουμε στο έργο μας, κανένα από αυτά δεν ήταν εφικτό. Χρειαζόμασταν κάτι άλλο. Χρειαζόμασταν κάτι που θα μας επέτρεπε να φορτώσουμε τα πρότυπά μας αποτελεσματικά, χωρίς ξεχωριστά αιτήματα δικτύου για καθένα από αυτά, ενώ παράλληλα μας επέτρεπε να γεμίζουμε πλήρως αυτά τα ίδια πρότυπα. Έτσι, αποφασίσαμε να εξετάσουμε τη χρήση ενός φορτωτή webpack που θα μας επιτρέψει να απαιτήσουμε τα πρότυπα μας στα συστατικά μας ως inline strings των HTML / γωνιακών προτύπων.

Τα οφέλη

Χρησιμοποιώντας τον html-loader webpack για τη φόρτωση όλων των αρχείων .html, ανακαλύψαμε ότι μπορέσαμε να φορτώσουμε αποτελεσματικά τα πρότυπα μας, ενώ παράλληλα μας επέτρεψε να επωφεληθούμε πλήρως από το lazyloading. Όταν χρησιμοποιείτε το πρότυπο: απαιτείται (syntax 'foo / bar / my.html'), το πακέτο webpack αντικαθιστά την απαιτούμενη εντολή σας με μια λειτουργία που καλείται και επιστρέφεται με τη συμβολοσειρά για το πρότυπο. Δεδομένου ότι το πρότυπο παρέχεται τώρα ως συμβολοσειρά html, αν το προϊόν τερματίσετε, το πρότυπο θα τεθεί επίσης σε λειτουργία. Αυτό ακριβώς χρειαζόμασταν. Ωστόσο, ανακαλύψαμε πολλά άλλα οφέλη, η ανακάλυψη των οποίων προκάλεσε αυτή τη θέση.

  • Γρήγορη αρχικοποίηση στοιχείων - Όταν χρησιμοποιείτε μια συμβολοσειρά inline ως πρότυπο, το στοιχείο μπορεί να αρχικοποιηθεί συγχρονισμένα. Χρησιμοποιώντας το templateUrl, το AngularJS θα ζητήσει το πρότυπο από το templateCache. Δεδομένου ότι το templateCache μπορεί να έχει ήδη το πρότυπο στην προσωρινή μνήμη του ή μπορεί να χρειαστεί να μεταβεί στο δίκτυο για να το πάρει, ζητώντας ένα πρότυπο από την προσωρινή μνήμη είναι μια διαδικασία που συμβαίνει ασύγχρονα. Ακόμα κι αν το πρότυπο βρίσκεται ήδη στη μνήμη cache, το templateCache θα επιστρέψει το ήδη αποθηκευμένο πρότυπο μέσω μιας κλήσης με υπόσχεση. Αυτό σημαίνει ότι το στοιχείο δεν μπορεί να αρχικοποιηθεί στον ίδιο βρόχο συμβάντων. Το αίτημα για το templateCache θα τοποθετείται πάντα στον επόμενο βρόχο συμβάντων, ακόμα και στα καλύτερα σενάρια. Αυτό σημαίνει ότι το στοιχείο μπορεί να αρχίσει να αρχικοποιεί, να ζητάει το πρότυπο του και στη συνέχεια να τερματίζει την αρχικοποίηση στον επόμενο βρόχο συμβάντων. Αλλά όταν χρησιμοποιείτε ένα inline συμβολοσειρά, το στοιχείο έχει ήδη το πρότυπο του έτοιμο, έτσι ώστε να μπορεί να ξεκινήσει και να τερματίσει την αρχικοποίησή του στον ίδιο βρόχο συμβάντων. Αυτό μπορεί να μην φαίνεται σημαντικό, αλλά είχε αρκετά απροσδόκητα αποτελέσματα που έπρεπε να αντισταθμίσουμε.
    - Τα συστατικά αρχικοποιούνται ταχύτερα - το οποίο ακούγεται φοβερό, AIR; Λοιπόν, είναι φοβερό. Εντούτοις, αυτό σημαίνει ότι ορισμένα από τα συστατικά σας που είχαν πάντοτε καθορισμένες τιμές εισόδου όταν ξεκίνησαν μπορεί να σπάσουν, προκαλώντας ίσες εκείνες τις ίδιες τιμές. Είχαμε πολλά σπασίματα συστατικών, εξαιτίας μη καθορισμένων τιμών δεσμευτικών εισροών. Έπρεπε να αλλάξουμε αυτά τα στοιχεία για να χρησιμοποιήσουμε $ watch ή $ onChanges για να ανιχνεύσουμε την ενημέρωση στις τιμές εισόδου.
    - Οι δοκιμές μονάδας θα εκτελούνται διαφορετικά - Επειδή οι δοκιμές γραφής αλλάζουν όταν κάνετε μια σύγχρονη δοκιμή έναντι μιας ασύγχρονης δοκιμής, η δοκιμή για αυτά τα εξαρτήματα μπορεί σίγουρα να αλλάξει. Για παράδειγμα, στο Mocha, εάν η δοκιμή σας είναι async, εισάγετε τη μέθοδο που ολοκληρώσατε στη δοκιμή σας και την ονομάζετε όταν ολοκληρωθεί η δοκιμή. Διαπιστώσαμε ότι οι δοκιμές εκτελούσαν πλέον συγχρονισμένα, πράγμα που σημαίνει ότι η ανάγκη έγχυσης δεν ήταν πλέον απαραίτητη. Επιπλέον, και είναι ενοχλητικό να το παραδεχτούμε, αλλά είχαμε δοκιμές που γράφτηκαν συγχρόνως, με τα πρότυπα να είναι async, οι δοκιμές ΔΕΝ ΠΟΤΕ επιτυχώς ολοκληρώθηκαν. Έτσι, όταν ανέλαβα τις αλλαγές για να εντάξω τα πρότυπα, τα τεστ αυτά άρχισαν να τρέχουν με επιτυχία, και αντί να περάσουν, αποτυγχάνουν !!!! Αρχικά σκέφτηκα ότι είχα σπάσει όλες αυτές τις δοκιμές. Μόλις μετά από 5 ώρες γύρισαν γύρω από αυτό συνειδητοποίησα ότι αυτές οι δοκιμές δεν πέρασαν ποτέ. Γι 'αυτό τώρα έχουμε τώρα αυξημένη κάλυψη δοκιμών τώρα που χρησιμοποιούμε inline πρότυπα.
  • ο html-loader χρησιμοποιεί ένα minifier html - Αυτό το μικρό γεγονός μείωσε άμεσα το μέγεθος των προτύπων μας κατά 19% σε ολόκληρη την εφαρμογή. Αυτό είναι τόσο εξαιρετικό και είναι πραγματικά κάτι που θα έπρεπε να κάνουμε εδώ και πολύ καιρό. Αναλύει επίσης τα πρότυπα και μας βοήθησε να βρούμε μερικές δωδεκάδες πρότυπα που είχαν άκυρο html σε αυτά. Πράγματα όπως: τάξη "blah", όπου το = λείπει. Ή χαρακτηριστικό = {{κάτι}}, το οποίο λείπει τα αποσπάσματα γύρω από όλα αυτά. Μόλις το έκανα, το κτίριο λειτούργησε ξανά.
  • ng-include ήταν ακόμα σπασμένα - Ενώ τα πρότυπα εξαρτημάτων λειτουργούσαν τώρα, τα ng-include's έχουν πλέον σπάσει. Πρέπει να βρούμε κάτι για αυτούς. Έτσι δημιουργήσαμε ένα μικρό προσαρμοσμένο φορτωτή, που θα φέρει το πρότυπο στο templateCache. Οι εσωτερικές μας πρακτικές μας λένε ότι δεν πρέπει να χρησιμοποιήσουμε το ng-include, αλλά εξακολουθούμε να έχουμε πολύ κώδικα 3+ ετών που τους περιέχει. Έτσι, αντί να επαναπροσδιορίσω όλα αυτά σε αυτή τη δέσμευση, χρησιμοποίησα αυτό το νέο φορτωτή και πήγα σε κάθε τμήμα της εφαρμογής που έχει ένα ng-include και φόρτωσε το πρότυπο για αυτό το τμήμα, όπως έχω δείξει παρακάτω. Αυτό σημαίνει ότι οι ng-includ είναι επίσης ληφθεί μέριμνα σε αυτή τη νέα διαδικασία.

Χρησιμοποιήθηκε JSCodeShift

Σας συνιστώ να χρησιμοποιήσετε την εφαρμογή webpack για εφαρμογές AngularJS και χρησιμοποιώντας το html-loader για να εισαγάγετε τα πρότυπα σας σε ετικέτες, πράγμα που σημαίνει ότι θα χρειαστεί να αλλάξετε τις παρουσίες του templateUrl σε στιγμιότυπα προτύπων. Δεδομένου ότι όλα αυτά φαίνονται πολύ διαφορετικά, αποφάσισα ότι αυτή ήταν μια πολύ καλή περίπτωση χρήσης για το JSCodeShift, ένα έργο από το Facebook που σας επιτρέπει να ανιχνεύσετε το AST και να αντικαταστήσετε προγραμματικά όλες τις περιπτώσεις για εσάς. Μπορείτε να το σκεφτείτε ως Εύρεση & Αντικατάσταση στα στεροειδή, με ένεση με περισσότερα στεροειδή. Ήταν πολύ απλό να γράψω το σενάριο που βρήκε και ενημέρωσε όλες αυτές τις χρήσεις του templateUrl: 'some / url / to.html με πρότυπο: require (). Ήμουν σε θέση να αλλάξω το 90% των χρήσεων με προγραμματισμό (περίπου 700 αρχεία), και έπρεπε να ολοκληρώσω τα τελευταία 70 με το χέρι. Θα μπορούσα να γράψω τον κώδικα για να τελειώσω αυτά τα άλλα 70, αλλά σκέφτηκα ότι θα μπορούσα να τα κάνω ευκολότερα με το χέρι από το να προσπαθώ να τα κωδικοποιήσω ξεχωριστά. Μια γρήγορη σημείωση, ο AST Explorer είναι απόλυτη ανάγκη όταν χρησιμοποιείτε το JSCodeShift. Χωρίς αυτήν, δεν θα είχα τη δυνατότητα να σημειώσω πρόοδο.

συμπέρασμα

Αποκτήστε τις εφαρμογές σας AngularJS σε μια κατασκευή webpack και βάλτε το χρόνο για να τις χρησιμοποιήσετε με τη χρήση του html-loader για να φορτώσετε τα πρότυπα. Χρησιμοποιήστε το πρότυπο αντί του templateUrl και, εάν δεν το έχετε ήδη κάνει, σταματήστε να χρησιμοποιείτε το ng-include. Και στη συνέχεια, lazyload, lazyload, lazyload! Κάποιες φορές οι άνθρωποι διακρίνουν μεταξύ καθυστερημένης φόρτωσης και τεμπέλης φόρτωσης. Αναφέρομαι τόσο σε καθυστερημένη φόρτωση όσο και σε τεμπέληνη φόρτωση όταν λέω "lazyload". Είναι η καλύτερη σας αλλαγή στη μείωση του χρόνου στο First Significant Paint και στη μείωση του χρόνου στη δημιουργία μιας εφαρμογής με την οποία μπορεί να αλληλεπιδράσει ο χρήστης. Καλή τύχη. Πίσω στα κεφάλια σου!