Καθώς οι εφαρμογές ιστού γίνονται ολοένα και πιο περίπλοκες, καθιστώντας την εφαρμογή ιστού σας επεκτάσιμη γίνεται εξαιρετικά σημαντική. Ενώ στο παρελθόν η συγγραφή ad-hoc JavaScript και jQuery θα αρκούσε, σήμερα η δημιουργία μιας εφαρμογής ιστού απαιτεί πολύ μεγαλύτερο βαθμό πειθαρχίας και επίσημες πρακτικές ανάπτυξης λογισμικού, όπως:
Ο Ιστός παρέχει επίσης μερικές από τις δικές του μοναδικές αναπτυξιακές προκλήσεις. Για παράδειγμα, δεδομένου ότι οι ιστοσελίδες υποβάλλουν πολλά ασύγχρονα αιτήματα, η απόδοση της εφαρμογής ιστού σας μπορεί να υποβαθμιστεί σημαντικά από το να χρειάζεται να ζητήσετε εκατοντάδες αρχεία JS και CSS, το καθένα με τα δικά του μικροσκοπικά γενικά έξοδα (κεφαλίδες, χειραψίες και ούτω καθεξής). Αυτό το συγκεκριμένο ζήτημα μπορεί συχνά να αντιμετωπιστεί συνδυάζοντας τα αρχεία, οπότε ζητάτε μόνο ένα ενιαίο αρχείο JS και CSS και όχι εκατοντάδες μεμονωμένα.
Είναι επίσης πολύ κοινό να χρησιμοποιείτε προεπεξεργαστές γλώσσας, όπως SASS και JSX που μεταγλωττίζονται σε εγγενή JS και CSS, καθώς και JS transpilers όπως το Babel, για να επωφεληθείτε από τον κώδικα ES6, διατηρώντας παράλληλα τη συμβατότητα ES5.
Αυτό ισοδυναμεί με σημαντικό αριθμό εργασιών που δεν έχουν καμία σχέση με τη σύνταξη της λογικής της ίδιας της εφαρμογής ιστού. Εδώ μπαίνουν οι δρομείς εργασίας. Ο σκοπός ενός δρομέα εργασιών είναι να αυτοματοποιήσει όλες αυτές τις εργασίες, έτσι ώστε να μπορείτε να επωφεληθείτε από ένα βελτιωμένο περιβάλλον ανάπτυξης ενώ εστιάζετε στη σύνταξη της εφαρμογής σας. Μόλις διαμορφωθεί ο δρομέας εργασιών, το μόνο που χρειάζεται να κάνετε είναι να καλέσετε μια μόνο εντολή σε ένα τερματικό.
Θα χρησιμοποιώ Χαψιά ως εκτελεστής εργασιών, επειδή είναι πολύ φιλικός για προγραμματιστές, εύκολο να μάθει και εύκολα κατανοητός.
Το API του Gulp αποτελείται από τέσσερις λειτουργίες:
gulp.src
gulp.dest
gulp.task
gulp.watch
Εδώ, για παράδειγμα, είναι ένα δείγμα εργασίας που χρησιμοποιεί τρεις από αυτές τις τέσσερις συναρτήσεις:
gulp.task('my-first-task', function() { gulp.src('/public/js/**/*.js') .pipe(concat()) .pipe(minify()) .pipe(gulp.dest('build')) });
Πότε my-first-task
εκτελείται, όλα τα αρχεία που ταιριάζουν με το μοτίβο glob /public/js/**/*.js
ελαχιστοποιούνται και μετά μεταφέρονται σε build
ντοσιέ.
Η ομορφιά αυτού είναι στο .pipe()
αλυσίδα. Παίρνετε ένα σύνολο αρχείων εισόδου, διοχετεύστε τα σε μια σειρά μετασχηματισμών και μετά επιστρέψτε τα αρχεία εξόδου. Για να γίνουν τα πράγματα ακόμα πιο βολικά, οι πραγματικοί μετασχηματισμοί σωληνώσεων, όπως minify()
, γίνονται συχνά από βιβλιοθήκες NPM. Ως αποτέλεσμα, στην πράξη είναι πολύ σπάνιο να χρειαστεί να γράψετε τους δικούς σας μετασχηματισμούς πέρα από τη μετονομασία αρχείων στο σωληνάριο.
Το επόμενο βήμα για την κατανόηση του Gulp είναι η κατανόηση της σειράς των εξαρτήσεων εργασιών.
gulp.task('my-second-task', ['lint', 'bundle'], function() { ... });
Εδώ, my-second-task
εκτελεί μόνο τη λειτουργία επιστροφής μετά το lint
και bundle
οι εργασίες ολοκληρώθηκαν. Αυτό επιτρέπει τον διαχωρισμό των ανησυχιών: Δημιουργείτε μια σειρά μικρών εργασιών με μία μόνο ευθύνη, όπως η μετατροπή LESS
να CSS
και να δημιουργήσετε ένα είδος κύριας εργασίας που απλά καλεί όλες τις άλλες εργασίες μέσω του πίνακα εξαρτήσεων εργασιών.
Τέλος, έχουμε gulp.watch
, το οποίο παρακολουθεί ένα μοτίβο αρχείου glob για αλλαγές και όταν εντοπίζεται μια αλλαγή, εκτελεί μια σειρά εργασιών.
gulp.task('my-third-task', function() { gulp.watch('/public/js/**/*.js', ['lint', 'reload']) })
Στο παραπάνω παράδειγμα, τυχόν αλλαγές σε ένα αρχείο που ταιριάζει /public/js/**/*.js
θα ενεργοποιήσει το lint
και reload
έργο. Μια κοινή χρήση του gulp.watch
είναι η ενεργοποίηση ζωντανών επαναφορτώσεων στο πρόγραμμα περιήγησης, μια λειτουργία που είναι τόσο χρήσιμη για ανάπτυξη που δεν θα μπορείτε να ζήσετε χωρίς αυτήν αφού την έχετε δοκιμάσει.
Και ακριβώς έτσι, καταλαβαίνετε όλα όσα πραγματικά πρέπει να ξέρετε για gulp
.
Όταν χρησιμοποιείτε το μοτίβο CommonJS, η ομαδοποίηση αρχείων JavaScript δεν είναι τόσο απλή όσο η συνένωση τους. Αντίθετα, έχετε ένα σημείο εισόδου (συνήθως ονομάζεται index.js
ή app.js
) με μια σειρά require
ή import
δηλώσεις στην κορυφή του αρχείου:
var Component1 = require('./components/Component1'); var Component2 = require('./components/Component2');
import Component1 from './components/Component1'; import Component2 from './components/Component2';
Οι εξαρτήσεις πρέπει να επιλυθούν πριν από τον υπόλοιπο κώδικα στο app.js
και αυτές οι εξαρτήσεις μπορεί να έχουν οι ίδιες περαιτέρω εξαρτήσεις προς επίλυση. Επιπλέον, μπορεί να require
την ίδια εξάρτηση σε πολλά μέρη της εφαρμογής σας, αλλά θέλετε μόνο να επιλύσετε αυτήν την εξάρτηση μία φορά. Όπως μπορείτε να φανταστείτε, όταν έχετε ένα δέντρο εξάρτησης σε βάθος λίγων επιπέδων, η διαδικασία ομαδοποίησης του JavaScript γίνεται μάλλον περίπλοκη. Αυτό είναι όπου οι δέσμες όπως Αναζήτηση και το Webpack έρχονται.
Webpack είναι ένα bundler ενώ ο Gulp είναι ένας δρομέας εργασιών, οπότε θα περίμενε κανείς να δει αυτά τα δύο εργαλεία που χρησιμοποιούνται συχνά μαζί. Αντ 'αυτού, υπάρχει μια αυξανόμενη τάση, ειδικά μεταξύ της κοινότητας του React, να χρησιμοποιεί το Webpack αντι αυτου του Γκουλπ. Γιατί είναι αυτό?
Με απλά λόγια, το Webpack είναι ένα τόσο ισχυρό εργαλείο που μπορεί ήδη να εκτελέσει τη συντριπτική πλειονότητα των εργασιών που διαφορετικά θα κάνατε μέσω ενός προγράμματος εκτέλεσης εργασιών. Για παράδειγμα, το Webpack παρέχει ήδη επιλογές για ελαχιστοποίηση και sourcemaps για το πακέτο σας. Επιπλέον, το Webpack μπορεί να εκτελεστεί ως ενδιάμεσο λογισμικό μέσω ενός προσαρμοσμένου διακομιστή που ονομάζεται webpack-dev-server
, ο οποίος υποστηρίζει τόσο τη ζωντανή επαναφόρτωση όσο και την καυτή επαναφόρτωση (θα μιλήσουμε για αυτές τις λειτουργίες αργότερα). Χρησιμοποιώντας τους φορτωτές, μπορείτε επίσης να προσθέσετε ES6 στον ESP transpilation και CSS πριν και μετά τους επεξεργαστές. Αυτό αφήνει πραγματικά τις δοκιμές μονάδας και το χνούδι ως σημαντικές εργασίες που το Webpack δεν μπορεί να χειριστεί ανεξάρτητα. Δεδομένου ότι έχουμε μειώσει τουλάχιστον μισές δωδεκάδες πιθανές εργασίες κόλπου σε δύο, πολλοί προγραμματιστές επιλέγουν να χρησιμοποιήσουν απευθείας σενάρια NPM, καθώς αυτό αποφεύγει τα γενικά έξοδα προσθήκης Gulp στο έργο (για τα οποία θα μιλήσουμε επίσης αργότερα) .
Το σημαντικότερο μειονέκτημα για τη χρήση του Webpack είναι ότι είναι μάλλον δύσκολο να διαμορφωθεί, κάτι που δεν είναι ελκυστικό εάν προσπαθείτε να ξεκινήσετε γρήγορα ένα έργο.
Θα δημιουργήσω ένα έργο με τρεις διαφορετικές ρυθμίσεις εκτέλεσης εργασιών. Κάθε εγκατάσταση θα εκτελεί τις ακόλουθες εργασίες:
Οι τρεις ρυθμίσεις μας θα είναι:
Η εφαρμογή θα χρησιμοποιηθεί Αντιδρώ για το front-end. Αρχικά, ήθελα να χρησιμοποιήσω μια προσέγγιση πλαισίου-αγνωστικής, αλλά η χρήση του React απλοποιεί πραγματικά τις ευθύνες του δρομέα εργασιών, καθώς απαιτείται μόνο ένα αρχείο HTML και το React λειτουργεί πολύ καλά με το μοτίβο CommonJS.
Θα καλύψουμε τα οφέλη και τα μειονεκτήματα κάθε εγκατάστασης, ώστε να μπορείτε να λάβετε μια τεκμηριωμένη απόφαση σχετικά με τον τύπο εγκατάστασης που ταιριάζει καλύτερα στις ανάγκες του έργου σας.
Έχω δημιουργήσει ένα αποθετήριο Git με τρεις κλάδους, έναν για κάθε προσέγγιση ( Σύνδεσμος ). Ο έλεγχος κάθε εγκατάστασης είναι τόσο απλό όσο:
git checkout npm prune (optional) npm install gulp (or npm start, depending on the setup)
Ας εξετάσουμε λεπτομερώς τον κώδικα σε κάθε κλάδο…
- app - components - fonts - styles - index.html - index.js - index.test.js - routes.js
Ένα απλό αρχείο HTML. Η εφαρμογή React φορτώνεται και χρησιμοποιούμε μόνο ένα ενιαίο αρχείο JS και CSS. Στην πραγματικότητα, στη ρύθμιση ανάπτυξης του Webpack, δεν χρειαζόμαστε καν bundle.css
.
Αυτό λειτουργεί ως το σημείο εισόδου JS της εφαρμογής μας. Ουσιαστικά, απλά φορτώνουμε το React Router στο div
με αναγνωριστικό app
που αναφέραμε νωρίτερα.
Αυτό το αρχείο καθορίζει τις διαδρομές μας. Οι διευθύνσεις url /
, /about
και /contact
χαρτογραφούνται στα HomePage
, AboutPage
, και ContactPage
συστατικά, αντίστοιχα.
Αυτή είναι μια σειρά δοκιμών μονάδας που ελέγχουν τη φυσική συμπεριφορά JavaScript. Σε μια πραγματική εφαρμογή ποιότητας παραγωγής, θα γράφατε ένα τεστ μονάδας ανά στοιχείο React (τουλάχιστον αυτά που χειρίζονται την κατάσταση), δοκιμάζοντας τη συμπεριφορά για συγκεκριμένες αντιδράσεις. Ωστόσο, για τους σκοπούς αυτής της ανάρτησης, αρκεί να έχετε απλώς μια δοκιμή λειτουργικής μονάδας που μπορεί να εκτελεστεί σε λειτουργία παρακολούθησης.
Αυτό μπορεί να θεωρηθεί ως το κοντέινερ για όλες τις προβολές σελίδας μας. Κάθε σελίδα περιέχει συστατικό καθώς και this.props.children
, το οποίο αξιολογεί την ίδια την προβολή σελίδας (ex / ContactPage
εάν στο /contact
στο πρόγραμμα περιήγησης).
Αυτή είναι η οικιακή μας άποψη. Επέλεξα τη χρήση react-bootstrap
δεδομένου ότι το σύστημα πλέγματος του bootstrap είναι εξαιρετικό για τη δημιουργία αποκριτικών σελίδων. Με τη σωστή χρήση του bootstrap, ο αριθμός των ερωτημάτων πολυμέσων που πρέπει να γράψετε για μικρότερα σημεία προβολής μειώνεται δραματικά.
Τα υπόλοιπα στοιχεία (Header
, AboutPage
, ContactPage
) έχουν παρόμοια δομή (σήμανση react-bootstrap
, χωρίς χειραγώγηση κατάστασης).
Τώρα ας μιλήσουμε περισσότερο για το στυλ.
Η προτιμώμενη προσέγγισή μου για το στυλ των στοιχείων React είναι να έχω ένα φύλλο στυλ ανά στοιχείο, του οποίου τα στυλ καλύπτονται μόνο για αυτό το συγκεκριμένο στοιχείο. Θα παρατηρήσετε ότι σε καθένα από τα στοιχεία του React, το ανώτερο επίπεδο div
έχει όνομα κλάσης που ταιριάζει με το όνομα του στοιχείου. Έτσι, για παράδειγμα, HomePage.js
έχει τη σήμανση τυλιγμένη από:
...
Υπάρχει επίσης ένα συσχετισμένο HomePage.scss
αρχείο που έχει δομή ως εξής:
@import '../../styles/variables'; .HomePage { // Content here }
Γιατί είναι τόσο χρήσιμη αυτή η προσέγγιση; Καταλήγει σε εξαιρετικά αρθρωτό CSS, εξαλείφοντας σε μεγάλο βαθμό το ζήτημα της ανεπιθύμητης αλληλουχίας.
Ας υποθέσουμε ότι έχουμε δύο στοιχεία React, Component1
και Component2
. Και στις δύο περιπτώσεις, θέλουμε να παρακάμψουμε το h2
μέγεθος γραμματοσειράς.
/* Component1.scss */ .Component1 { h2 { font-size: 30px; } } /* Component2.scss */ .Component2 { h2 { font-size: 60px; } }
Το h2
μέγεθος γραμματοσειράς Component1
και Component2
είναι ανεξάρτητα εάν τα στοιχεία είναι παρακείμενα ή ένα στοιχείο είναι τοποθετημένο μέσα στο άλλο. Στην ιδανική περίπτωση, αυτό σημαίνει ότι το στυλ ενός στοιχείου είναι εντελώς αυτόνομο, πράγμα που σημαίνει ότι το στοιχείο θα μοιάζει ακριβώς το ίδιο, ανεξάρτητα από το πού τοποθετείται στη σήμανση σας. Στην πραγματικότητα, δεν είναι πάντα τόσο απλό, αλλά είναι σίγουρα ένα τεράστιο βήμα προς τη σωστή κατεύθυνση.
Εκτός από τα στυλ ανά συστατικό, θέλω να έχω ένα styles
φάκελο που περιέχει ένα καθολικό φύλλο στυλ global.scss
, μαζί με τμήματα SASS που χειρίζονται μια συγκεκριμένη ευθύνη (σε αυτήν την περίπτωση, _fonts.scss
και _variables.scss
για γραμματοσειρές και μεταβλητές, αντίστοιχα). Το καθολικό φύλλο στυλ μας επιτρέπει να ορίσουμε τη γενική εμφάνιση και αίσθηση ολόκληρης της εφαρμογής, ενώ τα βοηθητικά τμήματα μπορούν να εισαχθούν από τα φύλλα στυλ ανά συστατικό, όπως απαιτείται.
Τώρα που ο κοινός κώδικας σε κάθε κλάδο έχει διερευνηθεί σε βάθος, ας στρέψουμε την εστίασή μας στην πρώτη προσέγγιση του runner / bundler εργασιών.
Αυτό βγαίνει σε ένα εκπληκτικά μεγάλο gulpfile, με 22 εισαγωγές και 150 γραμμές κώδικα. Για λόγους συντομίας, θα εξετάσω μόνο τα js
, css
, server
, watch
και default
εργασίες λεπτομερώς.
// Browserify specific configuration const b = browserify({ entries: [config.paths.entry], debug: true, plugin: PROD ? [] : [hmr, watchify], cache: {}, packageCache: {} }) .transform('babelify'); b.on('update', bundle); b.on('log', gutil.log); (...) gulp.task('js', bundle); (...) // Bundles our JS using Browserify. Sourcemaps are used in development, while minification is used in production. function bundle() { return b.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cond(PROD, minifyJS())) .pipe(cond(!PROD, sourcemaps.init({loadMaps: true}))) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)); }
Αυτή η προσέγγιση είναι μάλλον άσχημη για διάφορους λόγους. Για ένα πράγμα, η εργασία χωρίζεται σε τρία ξεχωριστά μέρη. Αρχικά, δημιουργείτε το αντικείμενο δέσμης Browserify b
, μεταβιβάζοντας ορισμένες επιλογές και καθορίζοντας ορισμένους χειριστές συμβάντων. Στη συνέχεια, έχετε την ίδια την εργασία Gulp, η οποία πρέπει να περάσει μια ονομαστική συνάρτηση ως επιστροφή κλήσης αντί να την ενσωματώσει (αφού b.on('update')
χρησιμοποιεί την ίδια επιστροφή κλήσης). Αυτό σχεδόν δεν έχει την κομψότητα μιας εργασίας Gulp όπου περνάτε απλώς σε ένα gulp.src
και διοχετεύστε μερικές αλλαγές.
Ένα άλλο ζήτημα είναι ότι αυτό μας αναγκάζει να έχουμε διαφορετικές προσεγγίσεις για την επαναφόρτωση html
, css
και js
στο πρόγραμμα περιήγησης. Κοιτάζοντας το Gulp μας watch
έργο:
gulp.task('watch', () => { livereload.listen({basePath: 'dist'}); gulp.watch(config.paths.html, ['html']); gulp.watch(config.paths.css, ['css']); gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Όταν αλλάζει ένα αρχείο HTML, το html
η εργασία εκτελείται εκ νέου.
gulp.task('html', () => { return gulp.src(config.paths.html) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
Οι τελευταίες κλήσεις σωλήνων livereload()
εάν το NODE_ENV
δεν είναι production
, η οποία ενεργοποιεί μια ανανέωση στο πρόγραμμα περιήγησης.
Η ίδια λογική χρησιμοποιείται για το ρολόι CSS. Όταν αλλάζει ένα αρχείο CSS, το css
η εργασία εκτελείται εκ νέου και ο τελευταίος σωλήνας στο css
ενεργοποιήσεις εργασιών livereload()
και ανανεώνει το πρόγραμμα περιήγησης.
Ωστόσο, το js
το ρολόι δεν καλεί το js
καθήκον καθόλου. Αντ 'αυτού, ο διαχειριστής συμβάντων του Browserify b.on('update', bundle)
χειρίζεται την επαναφόρτωση χρησιμοποιώντας μια εντελώς διαφορετική προσέγγιση (δηλαδή, αντικατάσταση καυτής μονάδας). Η ασυνέπεια σε αυτήν την προσέγγιση είναι ενοχλητική, αλλά δυστυχώς απαραίτητη για να έχουμε σταδιακή χτίζει. Αν απλώς κάλεσε αφελώς livereload()
στο τέλος του bundle
λειτουργία, αυτό θα ξαναχτίσει το ολόκληρος Πακέτο JS σε οποιαδήποτε μεμονωμένη αλλαγή αρχείου JS. Μια τέτοια προσέγγιση προφανώς δεν κλιμακώνεται. Όσο περισσότερα αρχεία JS έχετε, τόσο περισσότερο χρειάζεται κάθε ανάκαμψη. Ξαφνικά, τα 500 ms rebundles αρχίζουν να παίρνουν 30 δευτερόλεπτα, πράγμα που αναστέλλει πραγματικά την ευέλικτη ανάπτυξη.
gulp.task('css', () => { return gulp.src( [ 'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/font-awesome/css/font-awesome.css', config.paths.css ] ) .pipe(cond(!PROD, sourcemaps.init())) .pipe(sass().on('error', sass.logError)) .pipe(concat('bundle.css')) .pipe(cond(PROD, minifyCSS())) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
Το πρώτο ζήτημα εδώ είναι η δυσκίνητη συμπερίληψη CSS προμηθευτή. Κάθε φορά που προστίθεται ένα νέο αρχείο CSS προμηθευτή στο έργο, πρέπει να θυμόμαστε να αλλάζουμε το gulpfile για να προσθέσουμε ένα στοιχείο στο gulp.src
πίνακα, αντί να προσθέσουμε την εισαγωγή σε σχετική θέση στον πραγματικό πηγαίο κώδικα.
Το άλλο κύριο ζήτημα είναι η περίπλοκη λογική σε κάθε σωλήνα. Έπρεπε να προσθέσω μια βιβλιοθήκη NPM που ονομάζεται gulp-cond
απλώς για να ρυθμίσω τη λογική υπό όρους στους σωλήνες μου και το τελικό αποτέλεσμα δεν είναι πολύ ευανάγνωστο (τριπλές αγκύλες παντού!).
gulp.task('server', () => { nodemon({ script: 'server.js' }); });
Αυτή η εργασία είναι πολύ απλή. Είναι ουσιαστικά ένα περιτύλιγμα γύρω από την επίκληση της γραμμής εντολών nodemon server.js
, το οποίο εκτελείται server.js
σε περιβάλλον κόμβου. nodemon
χρησιμοποιείται αντί για node
έτσι ώστε τυχόν αλλαγές στο αρχείο να το κάνουν να επανεκκινήσει. Από προεπιλογή, nodemon
θα επανεκκινήσει την τρέχουσα διαδικασία όποιος Αλλαγή αρχείου JS, γι 'αυτό είναι σημαντικό να συμπεριλάβετε ένα nodemon.json
αρχείο για τον περιορισμό του πεδίου εφαρμογής του:
{ 'watch': 'server.js' }
Ας δούμε τον κωδικό διακομιστή μας.
const baseDir = process.env.NODE_ENV === 'production' ? 'build' : 'dist'; const port = process.env.NODE_ENV === 'production' ? 8080: 3000; const app = express();
Αυτό ορίζει τον βασικό κατάλογο του διακομιστή και της θύρας βάσει του περιβάλλοντος κόμβου και δημιουργεί μια παρουσία express.
app.use(require('connect-livereload')({port: 35729})); app.use(express.static(path.join(__dirname, baseDir)));
Αυτό προσθέτει connect-livereload
middleware (απαραίτητο για τη ρύθμιση ζωντανής επαναφόρτωσης) και στατικό middleware (απαραίτητο για το χειρισμό των στατικών στοιχείων μας).
app.get('/api/sample-route', (req, res) => { res.send({ website: 'ApeeScape', blogPost: true }); });
Αυτή είναι απλώς μια απλή διαδρομή API. Εάν πλοηγηθείτε στο localhost:3000/api/sample-route
στο πρόγραμμα περιήγησης, θα δείτε:
{ website: 'ApeeScape', blogPost: true }
Σε ένα πραγματικό backend, θα έχετε έναν ολόκληρο φάκελο αφιερωμένο σε διαδρομές API, ξεχωριστά αρχεία για τη δημιουργία συνδέσεων DB και ούτω καθεξής. Αυτό το δείγμα διαδρομή συμπεριλήφθηκε απλώς για να δείξει ότι μπορούμε εύκολα να δημιουργήσουμε ένα backend πάνω από το frontend που έχουμε δημιουργήσει.
app.get('*', (req, res) => { res.sendFile(path.join(__dirname, './', baseDir ,'/index.html')); });
Αυτή είναι μια συνολική διαδρομή, που σημαίνει ότι ανεξάρτητα από το url που πληκτρολογείτε στο πρόγραμμα περιήγησης, ο διακομιστής θα επιστρέψει τον μοναχικό μας index.html
σελίδα. Στη συνέχεια, είναι ευθύνη του React Router να επιλύσει τις διαδρομές μας από την πλευρά του πελάτη.
app.listen(port, () => { open(`http://localhost:${port}`); });
Αυτό λέει στη ρητή παρουσία μας να ακούσει τη θύρα που καθορίσαμε και να ανοίξει το πρόγραμμα περιήγησης σε μια νέα καρτέλα στη συγκεκριμένη διεύθυνση URL.
Μέχρι στιγμής το μόνο πράγμα που δεν μου αρέσει για τη ρύθμιση του διακομιστή είναι:
app.use(require('connect-livereload')({port: 35729}));
Δεδομένου ότι χρησιμοποιούμε ήδη gulp-livereload
στο gulpfile μας, αυτό δημιουργεί δύο ξεχωριστά μέρη όπου πρέπει να χρησιμοποιηθεί η φόρτωση του ήπατος.
Τώρα, τελευταίο αλλά όχι λιγότερο σημαντικό:
gulp.task('default', (cb) => { runSequence('clean', 'lint', 'test', 'html', 'css', 'js', 'fonts', 'server', 'watch', cb); });
Αυτή είναι η εργασία που εκτελείται όταν απλά πληκτρολογείτε gulp
στο τερματικό. Ένα περίεργο είναι η ανάγκη χρήσης runSequence
για να εκτελέσετε τις εργασίες να εκτελούνται διαδοχικά. Κανονικά, μια σειρά από εργασίες εκτελούνται παράλληλα, αλλά αυτή δεν είναι πάντα η επιθυμητή συμπεριφορά. Για παράδειγμα, πρέπει να έχουμε το clean
εκτέλεση εργασιών πριν από την html
για να διασφαλίσουμε ότι οι φάκελοι προορισμού μας είναι άδειοι πριν μετακινήσουμε αρχεία σε αυτούς. Όταν απελευθερωθεί το gulp 4, θα υποστηρίζει gulp.series
και gulp.parallel
μεθόδους εγγενώς, αλλά προς το παρόν πρέπει να φύγουμε με αυτό το ελαφρύ quirk στην εγκατάσταση μας.
Πέρα από αυτό, αυτό είναι πραγματικά πολύ κομψό. Η όλη δημιουργία και φιλοξενία της εφαρμογής μας πραγματοποιείται με μία μόνο εντολή και η κατανόηση οποιουδήποτε τμήματος της ροής εργασίας είναι τόσο απλή όσο η εξέταση μιας μεμονωμένης εργασίας στην ακολουθία εκτέλεσης. Επιπλέον, μπορούμε να χωρίσουμε ολόκληρη την ακολουθία σε μικρότερα κομμάτια για μια πιο αναλυτική προσέγγιση στη δημιουργία και τη φιλοξενία της εφαρμογής. Για παράδειγμα, θα μπορούσαμε να δημιουργήσουμε μια ξεχωριστή εργασία που ονομάζεται validate
που τρέχει το lint
και test
καθήκοντα. Ή θα μπορούσαμε να έχουμε ένα host
εργασία που εκτελείται server
και watch
. Αυτή η ικανότητα ενορχήστρωσης εργασιών είναι πολύ ισχυρή, ειδικά καθώς η εφαρμογή σας κλιμακώνεται και απαιτεί πιο αυτοματοποιημένες εργασίες.
if (argv.prod) { process.env.NODE_ENV = 'production'; } let PROD = process.env.NODE_ENV === 'production';
Χρησιμοποιώντας το yargs
Βιβλιοθήκη NPM, μπορούμε να παρέχουμε σημαίες γραμμής εντολών στο Gulp. Εδώ δίνω εντολή στο gulpfile να ορίσει το περιβάλλον κόμβου στην παραγωγή εάν --prod
μεταβιβάζεται στο gulp
στο τερματικό. Το PROD
Η μεταβλητή στη συνέχεια χρησιμοποιείται ως προϋπόθεση για τη διαφοροποίηση της συμπεριφοράς ανάπτυξης και παραγωγής στον κόλπο μας. Για παράδειγμα, μία από τις επιλογές που μεταβιβάζουμε στο browserify
Το config είναι:
plugin: PROD ? [] : [hmr, watchify]
Αυτό λέει browserify
να μην χρησιμοποιείτε πρόσθετα σε λειτουργία παραγωγής και να χρησιμοποιείτε hmr
και watchify
προσθήκες σε άλλα περιβάλλοντα.
Αυτό PROD
Η υπό όρους είναι πολύ χρήσιμη επειδή μας σώζει από το να πρέπει να γράψουμε ένα ξεχωριστό κόλπο για παραγωγή και ανάπτυξη, το οποίο τελικά θα περιέχει πολλή επανάληψη κώδικα. Αντ 'αυτού, μπορούμε να κάνουμε πράγματα όπως gulp --prod
για να εκτελέσετε την προεπιλεγμένη εργασία στην παραγωγή, ή gulp html --prod
για να εκτελέσετε μόνο το html
έργο στην παραγωγή. Από την άλλη πλευρά, είδαμε νωρίτερα ότι να σκουπίζουμε τους αγωγούς Gulp με δηλώσεις όπως .pipe(cond(!PROD, livereload()))
δεν είναι το πιο ευανάγνωστο. Στο τέλος, είναι θέμα προτίμησης εάν θέλετε να χρησιμοποιήσετε τη δυαδική μεταβλητή προσέγγιση ή να ρυθμίσετε δύο ξεχωριστά gulpfiles.
Τώρα ας δούμε τι συμβαίνει όταν συνεχίζουμε να χρησιμοποιούμε το Gulp ως δρομέα εργασιών μας, αλλά αντικαθιστούμε το Browserify με το Webpack.
Ξαφνικά το gulpfile μας έχει μήκος μόνο 99 γραμμές με 12 εισαγωγές, μια μείωση από την προηγούμενη εγκατάσταση! Εάν ελέγξουμε την προεπιλεγμένη εργασία:
gulp.task('default', (cb) => { runSequence('lint', 'test', 'build', 'server', 'watch', cb); });
Τώρα η πλήρης εγκατάσταση της εφαρμογής ιστού απαιτεί μόνο πέντε εργασίες αντί για εννέα, μια δραματική βελτίωση.
Επιπλέον, έχουμε εξαλείψει την ανάγκη για livereload
. Το watch
η εργασία είναι τώρα απλά:
gulp.task('watch', () => { gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Αυτό σημαίνει ότι ο παρατηρητής του κόλπου μας δεν προκαλεί κανένα είδος συμπεριφοράς ανατροπής. Ως πρόσθετο μπόνους, δεν χρειάζεται να μεταφέρουμε index.html
από app
έως dist
ή build
πια.
Επιστρέφοντας την εστίασή μας στη μείωση εργασιών, τα html
, css
, js
και fonts
όλες οι εργασίες έχουν αντικατασταθεί από ένα μόνο build
έργο:
gulp.task('build', () => { runSequence('clean', 'html'); return gulp.src(config.paths.entry) .pipe(webpack(require('./webpack.config'))) .pipe(gulp.dest(config.paths.baseDir)); });
Αρκετά απλό. Εκτελέστε το clean
και html
εργασίες διαδοχικά. Μόλις ολοκληρωθούν, πάρτε το σημείο εισόδου μας, περάστε το μέσω του Webpack, περνώντας σε ένα webpack.config.js
αρχείο για να το διαμορφώσετε και να στείλετε το προκύπτον πακέτο στο baseDir
(είτε dist
ή build
, ανάλογα με τον κόμβο env).
Ας ρίξουμε μια ματιά στο αρχείο διαμόρφωσης Webpack:
Αυτό είναι ένα αρκετά μεγάλο και εκφοβιστικό αρχείο ρυθμίσεων, οπότε ας εξηγήσουμε μερικές από τις σημαντικές ιδιότητες που τίθενται στο module.exports
αντικείμενο.
devtool: PROD ? 'source-map' : 'eval-source-map',
Αυτό καθορίζει τον τύπο sourcemaps που θα χρησιμοποιήσει το Webpack. Όχι μόνο το Webpack υποστηρίζει το sourcemaps έξω από το κουτί, αλλά υποστηρίζει πραγματικά ένα ευρύ φάσμα επιλογών sourcemap. Κάθε επιλογή παρέχει μια διαφορετική ισορροπία της λεπτομέρειας του sourcemap έναντι της ταχύτητας ανακατασκευής (ο χρόνος που απαιτείται για την επιστροφή των αλλαγών). Αυτό σημαίνει ότι μπορούμε να χρησιμοποιήσουμε μια «φτηνή» επιλογή sourcemap για ανάπτυξη για να επιτύχουμε γρήγορες επαναφορτώσεις και μια πιο ακριβή επιλογή sourcemap στην παραγωγή.
entry: PROD ? './app/index' : [ 'webpack-hot-middleware/client?reload=true', // reloads the page if hot module reloading fails. './app/index' ]
Αυτό είναι το σημείο εισόδου του πακέτου μας. Παρατηρήστε ότι ένας πίνακας έχει περάσει, που σημαίνει ότι είναι δυνατό να υπάρχουν πολλά σημεία εισόδου. Σε αυτήν την περίπτωση, έχουμε το αναμενόμενο σημείο εισόδου app/index.js
καθώς και το webpack-hot-middleware
σημείο εισόδου που χρησιμοποιείται ως μέρος της εγκατάστασης επαναφόρτωσης της λειτουργικής μονάδας.
output: { path: PROD ? __dirname + '/build' : __dirname + '/dist', publicPath: '/', filename: 'bundle.js' },
Αυτό είναι το σημείο όπου θα δημιουργηθεί το μεταγλωττισμένο πακέτο. Η πιο συγκεχυμένη επιλογή είναι publicPath
. Ορίζει τη διεύθυνση URL βάσης για το πού θα φιλοξενείται το πακέτο σας στο διακομιστή. Έτσι, για παράδειγμα, εάν το publicPath
είναι /public/assets
, τότε το πακέτο θα εμφανιστεί κάτω από το /public/assets/bundle.js
στον διακομιστή.
devServer: { contentBase: PROD ? './build' : './app' }
Αυτό λέει στον διακομιστή ποιον φάκελο στο έργο σας θα χρησιμοποιήσει ως ριζικός κατάλογος του διακομιστή.
Εάν έχετε μπερδευτεί ποτέ για το πώς το Webpack χαρτογραφεί το δημιουργημένο πακέτο στο έργο σας στο πακέτο στο διακομιστή, απλώς θυμηθείτε τα εξής:
path
+ filename
: Η ακριβής τοποθεσία του πακέτου στον πηγαίο κώδικα του έργου σαςcontentBase
(ως root, /
) + publicPath
: Η θέση του πακέτου στο διακομιστήplugins: PROD ? [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin(GLOBALS), new ExtractTextPlugin('bundle.css'), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) ] : [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ],
Πρόκειται για πρόσθετα που βελτιώνουν με κάποιο τρόπο τη λειτουργικότητα του Webpack. Για παράδειγμα, webpack.optimize.UglifyJsPlugin
είναι υπεύθυνος για την ελαχιστοποίηση.
loaders: [ {test: /.js$/, include: path.join(__dirname, 'app'), loaders: ['babel']}, { test: /.css$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap'): 'style!css?sourceMap' }, { test: /.scss$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap!resolve-url!sass?sourceMap') : 'style!css?sourceMap!resolve-url!sass?sourceMap' }, gif)(?S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]', woff2 ]
Αυτά είναι φορτωτές. Ουσιαστικά, προεπεξεργάζονται αρχεία που φορτώνονται μέσω require()
δηλώσεις. Είναι κάπως παρόμοια με τους σωλήνες Gulp, καθώς μπορείτε να συνδέσετε τους φορτωτές μαζί.
Ας εξετάσουμε ένα από τα αντικείμενα του φορτωτή μας:
{test: /.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'}
Το test
Η ιδιότητα λέει στο Webpack ότι ο συγκεκριμένος φορτωτής ισχύει εάν ένα αρχείο ταιριάζει με το παρεχόμενο μοτίβο regex, σε αυτήν την περίπτωση /.scss$/
. Το loader
Η ιδιότητα αντιστοιχεί στην ενέργεια που εκτελεί ο φορτωτής. Εδώ συνδέουμε μαζί τα style
, css
, resolve-url
και sass
φορτωτές, οι οποίοι εκτελούνται με αντίστροφη σειρά.
Πρέπει να παραδεχτώ ότι δεν βρίσκω το loader3!loader2!loader1
σύνταξη πολύ κομψή. Μετά από όλα, πότε πρέπει ποτέ να διαβάσετε τίποτα σε ένα πρόγραμμα από δεξιά προς τα αριστερά; Παρ 'όλα αυτά, οι φορτωτές είναι ένα πολύ ισχυρό χαρακτηριστικό του webpack. Στην πραγματικότητα, ο φορτωτής που μόλις ανέφερα μας επιτρέπει να εισάγουμε αρχεία SASS απευθείας στο JavaScript μας! Για παράδειγμα, μπορούμε να εισαγάγουμε τον προμηθευτή και τα καθολικά φύλλα στυλ στο αρχείο σημείου εισόδου:
import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; // CSS imports import '../node_modules/bootstrap/dist/css/bootstrap.css'; import '../node_modules/font-awesome/css/font-awesome.css'; import './styles/global.scss'; render(, document.getElementById('app'));
Ομοίως, στο στοιχείο Header μπορούμε να προσθέσουμε import './Header.scss'
για να εισαγάγετε το σχετικό φύλλο στυλ του στοιχείου. Αυτό ισχύει και για όλα τα άλλα συστατικά μας.
Κατά τη γνώμη μου, αυτό μπορεί σχεδόν να θεωρηθεί επαναστατική αλλαγή στον κόσμο της ανάπτυξης JavaScript. Δεν χρειάζεται να ανησυχείτε για ομαδοποίηση, ελαχιστοποίηση ή χάρτες αποθήκευσης CSS, καθώς ο φορτωτής μας χειρίζεται όλα αυτά για εμάς. Ακόμη και η επαναφόρτωση της λειτουργικής μονάδας λειτουργεί για τα αρχεία CSS μας. Τότε η δυνατότητα χειρισμού των εισαγωγών JS και CSS στο ίδιο αρχείο καθιστά την ανάπτυξη εννοιολογική απλούστερη: Περισσότερη συνέπεια, λιγότερη εναλλαγή περιβάλλοντος και ευκολότερη αιτιολόγηση.
Για μια σύντομη περίληψη του τρόπου λειτουργίας αυτής της δυνατότητας: Το Webpack ενσωματώνει το CSS στο πακέτο JS. Στην πραγματικότητα, το Webpack μπορεί να το κάνει και για εικόνες και γραμματοσειρές:
gif)(?S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]', ttf)(?S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'
Το URL loader δίνει εντολή στο Webpack να ενσωματώσει τις εικόνες και τις γραμματοσειρές μας ως url δεδομένων αν είναι κάτω από 100 KB, διαφορετικά να τις εξυπηρετούν ως ξεχωριστά αρχεία. Φυσικά, μπορούμε επίσης να διαμορφώσουμε το μέγεθος αποκοπής σε διαφορετική τιμή, όπως 10 KB.
Με λίγα λόγια, αυτή είναι η διαμόρφωση Webpack. Θα παραδεχτώ ότι υπάρχει αρκετή ρύθμιση, αλλά τα οφέλη από τη χρήση του είναι απλά φαινομενικά. Παρόλο που το Browserify διαθέτει πρόσθετα και μετασχηματισμούς, απλά δεν μπορούν να συγκριθούν με τους φορτωτές Webpack όσον αφορά την πρόσθετη λειτουργικότητα.
Σε αυτήν τη ρύθμιση, χρησιμοποιούμε σενάρια npm απευθείας αντί να βασίζουμε σε ένα gulpfile για την αυτοματοποίηση των εργασιών μας.
'scripts': { 'start': 'npm-run-all --parallel lint:watch test:watch build', 'start:prod': 'npm-run-all --parallel lint test build:prod', 'clean-dist': 'rimraf ./dist && mkdir dist', 'clean-build': 'rimraf ./build && mkdir build', 'clean': 'npm-run-all clean-dist clean-build', 'test': 'mocha ./app/**/*.test.js --compilers js:babel-core/register', 'test:watch': 'npm run test -- --watch', 'lint': 'esw ./app/**/*.js', 'lint:watch': 'npm run lint -- --watch', 'server': 'nodemon server.js', 'server:prod': 'cross-env NODE_ENV=production nodemon server.js', 'build-html': 'node tools/buildHtml.js', 'build-html:prod': 'cross-env NODE_ENV=production node tools/buildHtml.js', 'prebuild': 'npm-run-all clean-dist build-html', 'build': 'webpack', 'postbuild': 'npm run server', 'prebuild:prod': 'npm-run-all clean-build build-html:prod', 'build:prod': 'cross-env NODE_ENV=production webpack', 'postbuild:prod': 'npm run server:prod' }
Για να εκτελέσετε ανάπτυξη και παραγωγή, εισαγάγετε npm start
και npm run start:prod
, αντίστοιχα.
Αυτό είναι σίγουρα πιο συμπαγές από το gulpfile, δεδομένου ότι έχουμε κόψει 99 έως 150 γραμμές κώδικα σε 19 σενάρια NPM ή 12 εάν εξαιρέσουμε τα σενάρια παραγωγής (τα περισσότερα από τα οποία αντικατοπτρίζουν μόνο τα σενάρια ανάπτυξης με το περιβάλλον κόμβου που έχει οριστεί στην παραγωγή ). Το μειονέκτημα είναι ότι αυτές οι εντολές είναι κάπως κρυπτικές σε σύγκριση με τις αντίστοιχες εργασίες του Gulp και δεν είναι τόσο εκφραστικές. Για παράδειγμα, δεν υπάρχει τρόπος (τουλάχιστον που γνωρίζω) να έχουμε ένα σενάριο npm να εκτελεί συγκεκριμένες εντολές σε σειρά και άλλες παράλληλα. Είναι είτε το ένα είτε το άλλο.
Ωστόσο, υπάρχει ένα τεράστιο πλεονέκτημα σε αυτήν την προσέγγιση. Χρησιμοποιώντας βιβλιοθήκες NPM όπως mocha
απευθείας από τη γραμμή εντολών, δεν χρειάζεται να εγκαταστήσετε ένα ισοδύναμο περιτύλιγμα Gulp για κάθε (σε αυτήν την περίπτωση, gulp-mocha
).
Αντί για εγκατάσταση NPM
Εγκαθιστούμε τα ακόλουθα πακέτα:
Παραθέτοντας την ανάρτηση του Cory House, Γιατί άφησα το Gulp και το Grunt για σενάρια NPM :
Ήμουν μεγάλος θαυμαστής του Gulp. Αλλά στο τελευταίο μου έργο, κατέληξα με εκατοντάδες γραμμές στο gulpfile και περίπου δώδεκα προσθήκες Gulp. Δυσκολεύομαι να ενσωματώσω το Webpack, το Browsersync, το hot reload, το Mocha και πολλά άλλα χρησιμοποιώντας το Gulp. Γιατί; Λοιπόν, ορισμένα πρόσθετα δεν είχαν επαρκή τεκμηρίωση για τη θήκη χρήσης μου. Ορισμένες προσθήκες εξέθεσαν μόνο μέρος του API που χρειαζόμουν. Κάποιος είχε ένα περίεργο σφάλμα όπου θα παρακολουθούσε μόνο ένα μικρό αριθμό αρχείων. Άλλα απογυμνωμένα χρώματα κατά την έξοδο στη γραμμή εντολών.
Προσδιορίζει τρία βασικά ζητήματα με τον Gulp:
Θα τείνω να συμφωνώ με όλα αυτά.
Όποτε μια βιβλιοθήκη όπως eslint
ενημερώνεται, το σχετικό gulp-eslint
η βιβλιοθήκη χρειάζεται μια αντίστοιχη ενημέρωση. Εάν ο συντηρητής της βιβλιοθήκης χάσει το ενδιαφέρον, η εκκεντρική έκδοση της βιβλιοθήκης δεν συγχρονίζεται με τη μητρική. Το ίδιο ισχύει και όταν δημιουργείται μια νέα βιβλιοθήκη. Εάν κάποιος δημιουργήσει μια βιβλιοθήκη xyz
και πιάνει, ξαφνικά χρειάζεστε ένα αντίστοιχο gulp-xyz
βιβλιοθήκη για να το χρησιμοποιήσετε στις εργασίες σας.
Κατά μία έννοια, αυτή η προσέγγιση δεν κλιμακώνεται. Στην ιδανική περίπτωση, θα θέλαμε μια προσέγγιση όπως το Gulp που μπορεί να χρησιμοποιήσει τις εγγενείς βιβλιοθήκες.
Αν και βιβλιοθήκες όπως gulp-plumber
συμβάλλετε στην ανακούφιση αυτού του ζητήματος, είναι αλήθεια ότι η αναφορά σφαλμάτων στο gulp
απλά δεν είναι πολύ χρήσιμο. Εάν ακόμη και ένας σωλήνας ρίξει μια εξαίρεση χωρίς χειρισμό, λαμβάνετε ένα ίχνος στοίβας για ένα ζήτημα που φαίνεται εντελώς άσχετο με αυτό που προκαλεί το πρόβλημα στον πηγαίο κώδικα. Αυτό μπορεί να κάνει τον εντοπισμό σφαλμάτων έναν εφιάλτη σε ορισμένες περιπτώσεις. Κανένα ποσό αναζήτησης στο Google ή στο Stack Overflow δεν μπορεί πραγματικά να σας βοηθήσει εάν το σφάλμα είναι αρκετά κρυφό ή παραπλανητικό.
Συχνά το βρίσκω μικρό gulp
Οι βιβλιοθήκες τείνουν να έχουν πολύ περιορισμένη τεκμηρίωση. Υποψιάζομαι ότι αυτό συμβαίνει επειδή ο συγγραφέας κάνει συνήθως τη βιβλιοθήκη κυρίως για δική του χρήση. Επιπλέον, είναι συνηθισμένο να εξετάζουμε την τεκμηρίωση τόσο για την προσθήκη Gulp όσο και για την ίδια τη μητρική βιβλιοθήκη, πράγμα που σημαίνει πολλή εναλλαγή περιβάλλοντος και διπλάσια ανάγνωση.
Μου φαίνεται αρκετά σαφές ότι το Webpack είναι προτιμότερο από το Browserify και τα σενάρια NPM είναι προτιμότερα από το Gulp, αν και κάθε επιλογή έχει τα οφέλη και τα μειονεκτήματά της. Το Gulp είναι σίγουρα πιο εκφραστικό και βολικό στη χρήση από τα σενάρια NPM, αλλά πληρώνετε το τίμημα σε όλη την προστιθέμενη αφαίρεση.
Όχι κάθε συνδυασμός μπορεί να είναι τέλειος για την εφαρμογή σας, αλλά αν θέλετε να αποφύγετε έναν τεράστιο αριθμό εξαρτήσεων ανάπτυξης και μια απογοητευτική εμπειρία εντοπισμού σφαλμάτων, το Webpack με σενάρια NPM είναι ο τρόπος να πάτε. Ελπίζω να βρείτε αυτό το άρθρο χρήσιμο στην επιλογή των σωστών εργαλείων για το επόμενο έργο σας.
Σχετίζεται με: