Gulp: Tajna broń programisty stron internetowych do maksymalizacji szybkości witryny

Opublikowany: 2022-03-11

Wielu z nas ma do czynienia z projektami internetowymi, które są wykorzystywane w produkcji, które świadczą różne usługi dla społeczeństwa. W przypadku takich projektów ważne jest, aby móc szybko zbudować i wdrożyć nasz kod. Robienie czegoś szybko często prowadzi do błędów, zwłaszcza jeśli proces jest powtarzalny, dlatego dobrą praktyką jest maksymalne zautomatyzowanie takiego procesu.

Gulp: Tajna broń programisty stron internetowych do maksymalizacji szybkości witryny

Moi koledzy programiści: Nie ma usprawiedliwienia dla serwowania śmieci w przeglądarce.
Ćwierkać

W tym poście przyjrzymy się narzędziu, które może być częścią tego, co pozwoli nam osiągnąć taką automatyzację. To narzędzie to pakiet npm o nazwie Gulp.js. Aby zapoznać się z podstawową terminologią Gulp.js używaną w tym poście, zapoznaj się z „Wprowadzeniem do automatyzacji JavaScript z Gulp”, które zostało wcześniej opublikowane na blogu przez Antoniosa Minasa, jednego z naszych kolegów programistów Toptal. Założymy podstawową znajomość środowiska npm, ponieważ jest ono szeroko używane w tym poście do instalowania pakietów.

Udostępnianie zasobów frontonu

Zanim przejdziemy dalej, cofnijmy się o kilka kroków, aby uzyskać przegląd problemu, który może dla nas rozwiązać Gulp.js. Wiele projektów internetowych zawiera frontendowe pliki JavaScript, które są udostępniane klientowi w celu zapewnienia różnych funkcjonalności stronie internetowej. Zwykle istnieje również zestaw arkuszy stylów CSS, które są również udostępniane klientowi. Czasami, patrząc na kod źródłowy strony internetowej lub aplikacji internetowej, możemy zobaczyć kod taki:

 <link href="css/main.css" rel="stylesheet"> <link href="css/custom.css" rel="stylesheet"> <script src="js/jquery.min.js"></script> <script src="js/site.js"></script> <script src="js/module1.js"></script> <script src="js/module2.js"></script>

Jest kilka problemów z tym kodem. Zawiera odniesienia do dwóch oddzielnych arkuszy stylów CSS i czterech oddzielnych plików JavaScript. Oznacza to, że serwer musi wysłać do serwera łącznie sześć żądań, a każde żądanie musi osobno załadować zasób, zanim strona będzie gotowa. Jest to mniejszy problem w przypadku HTTP/2, ponieważ HTTP/2 wprowadza równoległość i kompresję nagłówków, ale nadal jest to problem. Zwiększa całkowity ruch wymagany do załadowania tej strony i obniża jakość obsługi użytkownika, ponieważ ładowanie plików trwa dłużej. W przypadku HTTP 1.1 przechwytuje również sieć i zmniejsza liczbę dostępnych kanałów żądań. O wiele lepiej byłoby połączyć pliki CSS i JavaScript w jeden pakiet dla każdego z nich. W ten sposób byłyby tylko dwie prośby. Przydałoby się również obsłużyć zminimalizowane wersje tych plików, które zwykle są znacznie mniejsze niż oryginały. Nasza aplikacja internetowa może się również zepsuć, jeśli którykolwiek z zasobów zostanie zbuforowany, a klient otrzyma nieaktualną wersję.

Przeciążać

Jednym z prymitywnych sposobów rozwiązania niektórych z tych problemów jest ręczne połączenie każdego typu zasobu w pakiet za pomocą edytora tekstu, a następnie uruchomienie wyniku za pomocą usługi minifikatora, takiej jak http://jscompress.com/. Okazuje się to bardzo żmudne, aby robić to w sposób ciągły podczas procesu rozwoju. Niewielkim, ale wątpliwym ulepszeniem byłoby hostowanie naszego własnego serwera miniifier, przy użyciu jednego z pakietów dostępnych na GitHub. Wtedy moglibyśmy zrobić rzeczy, które wyglądałyby trochę podobnie do tego:

 <script src="min/f=js/site.js,js/module1.js"></script>

To serwowałoby naszemu klientowi zminimalizowane pliki, ale nie rozwiązałoby problemu buforowania. Spowodowałoby to również dodatkowe obciążenie serwera, ponieważ nasz serwer musiałby zasadniczo łączyć i minimalizować wszystkie pliki źródłowe powtarzalnie przy każdym żądaniu.

Automatyzacja za pomocą Gulp.js

Z pewnością możemy zrobić lepiej niż jedno z tych dwóch podejść. To, czego naprawdę chcemy, to zautomatyzowanie bundlingu i włączenie go w fazę budowania naszego projektu. Chcemy skończyć z gotowymi pakietami zasobów, które są już zminimalizowane i gotowe do wyświetlania. Chcemy również zmusić klienta do otrzymywania najbardziej aktualnych wersji naszych zasobów w pakiecie na każde żądanie, ale nadal chcemy korzystać z pamięci podręcznej, jeśli to możliwe. Na szczęście dla nas Gulp.js sobie z tym poradzi. W dalszej części artykułu będziemy budować rozwiązanie, które będzie wykorzystywać moc Gulp.js do łączenia i minimalizowania plików. Będziemy również używać wtyczki, aby zepsuć pamięć podręczną, gdy pojawią się aktualizacje.

W naszym przykładzie stworzymy następującą strukturę katalogów i plików:

 public/ |- build/ |- js/ |- bundle-{hash}.js |- css/ |- stylesheet-{hash}.css assets/ |- js/ |- vendor/ |- jquery.js |- site.js |- module1.js |- module2.js |- css/ |- main.css |- custom.css gulpfile.js package.json
npm sprawia, że ​​zarządzanie pakietami w projektach Node.js to prawdziwa przyjemność. Gulp zapewnia ogromną rozszerzalność, wykorzystując proste podejście do tworzenia pakietów npm w celu dostarczania modułowych i wydajnych wtyczek.

W pliku gulpfile.js zdefiniujemy zadania, które Gulp dla nas wykona. Plik package.json jest używany przez npm do definiowania pakietu naszej aplikacji i śledzenia zależności, które będziemy instalować. Publiczny katalog powinien być skonfigurowany, aby stawić czoła sieci. W katalogu asset będziemy przechowywać nasze pliki źródłowe. Aby użyć Gulpa w projekcie, będziemy musieli zainstalować go przez npm i zapisać jako zależność deweloperską dla projektu. Będziemy również chcieli zacząć od wtyczki concat dla Gulp, która pozwoli nam połączyć wiele plików w jeden.

Aby zainstalować te dwa elementy, uruchomimy następujące polecenie:

 npm install --save-dev gulp gulp-concat

Następnie będziemy chcieli rozpocząć pisanie zawartości pliku gulpfile.js.

 var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

Tutaj ładujemy bibliotekę gulp i jej wtyczkę concat. Następnie definiujemy trzy zadania.

Ładowanie biblioteki gulp i jej wtyczki concat

Pierwsze zadanie ( pack-js ) definiuje procedurę kompresji wielu plików źródłowych JavaScript w jeden pakiet. Wypisujemy pliki źródłowe, które będą globowane, odczytywane i łączone w określonej kolejności. Przesyłamy to do wtyczki concat, aby uzyskać jeden końcowy plik o nazwie bundle.js . Na koniec mówimy gulpowi, aby zapisał plik do public/build/js .

Drugie zadanie ( pack-css ) robi to samo, co powyżej, ale dla arkuszy stylów CSS. Mówi Gulpowi, aby przechowywał połączone dane wyjściowe jako stylesheet.css w public/build/css .

Trzecie zadanie ( default ) to to, które Gulp uruchamia, gdy wywołujemy je bez argumentów. W drugim parametrze przekazujemy listę innych zadań do wykonania po uruchomieniu zadania domyślnego.

Wklejmy ten kod do gulpfile.js za pomocą dowolnego edytora kodu źródłowego, którego zwykle używamy, a następnie zapiszmy plik w katalogu głównym aplikacji.

Następnie otworzymy wiersz poleceń i uruchomimy:

 gulp

Jeśli spojrzymy na nasze pliki po uruchomieniu tego polecenia, znajdziemy dwa nowe pliki: public/build/js/bundle.js i public/build/css/stylesheet.css . Są to konkatenacje naszych plików źródłowych, co rozwiązuje część pierwotnego problemu. Jednak nie są one minimalizowane i nie ma jeszcze pomijania pamięci podręcznej. Dodajmy automatyczną minifikację.

Optymalizacja budowanych aktywów

Będziemy potrzebować dwóch nowych wtyczek. Aby je dodać, uruchomimy następujące polecenie:

 npm install --save-dev gulp-clean-css gulp-minify

Pierwsza wtyczka służy do minifikacji CSS, a druga do minifikacji JavaScript. Pierwsza korzysta z pakietu clean-css, a druga z pakietu UglifyJS2. Najpierw załadujemy te dwa pakiety do naszego gulpfile.js:

 var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');

Będziemy musieli użyć ich w naszych zadaniach tuż przed zapisaniem danych wyjściowych na dysku:

 .pipe(minify()) .pipe(cleanCss())

Plik gulpfile.js powinien teraz wyglądać tak:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify()) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

Znowu przełknijmy łyk. Zobaczymy, że plik stylesheet.css jest zapisany w formacie zminimalizowanym, a plik bundle.js nadal jest zapisany bez zmian. Zauważymy, że mamy teraz także pakiet min.js, który jest zminimalizowany. Chcemy tylko zminifikowany plik i chcemy, aby był zapisany jako bundle.js , więc zmodyfikujemy nasz kod o dodatkowe parametry:

 .pipe(minify({ ext:{ min:'.js' }, noSource: true }))

Zgodnie z dokumentacją wtyczki gulp-minify (https://www.npmjs.com/package/gulp-minify), ustawi to żądaną nazwę dla wersji zminimalizowanej i powie wtyczce, aby nie tworzyła wersji zawierającej oryginalne źródło. Jeśli usuniemy zawartość katalogu build i ponownie uruchomimy gulp z wiersza poleceń, otrzymamy tylko dwa zminimalizowane pliki. Właśnie zakończyliśmy wdrażanie fazy minifikacji naszego procesu budowania.

Pomijanie pamięci podręcznej

Następnie będziemy chcieli dodać pomijanie pamięci podręcznej i będziemy musieli zainstalować wtyczkę do tego:

 npm install --save-dev gulp-rev

I wymagaj tego w naszym pliku Gulp:

 var rev = require('gulp-rev');

Korzystanie z wtyczki jest nieco trudne. Najpierw musimy przepuścić zminifikowane wyjście przez wtyczkę. Następnie musimy ponownie wywołać wtyczkę po zapisaniu wyników na dysku. Wtyczka zmienia nazwy plików, tak aby były oznaczone unikalnym hashem, a także tworzy plik manifestu. Plik manifestu to mapa, której nasza aplikacja może użyć do określenia najnowszych nazw plików, do których powinniśmy się odwoływać w naszym kodzie HTML. Po zmodyfikowaniu pliku gulp powinien wyglądać tak:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('default', ['pack-js', 'pack-css']);
Dzięki odpowiedniemu pomijaniu pamięci podręcznej możesz zwariować z powodu długiego czasu wygaśnięcia plików JS i CSS i w razie potrzeby niezawodnie zastąpić je nowszymi wersjami.

Usuńmy zawartość naszego katalogu kompilacji i ponownie uruchom gulp. Przekonamy się, że mamy teraz dwa pliki z hashtagami dołączonymi do każdej z nazw plików oraz manifest.json zapisany w public/build . Jeśli otworzymy plik manifestu, zobaczymy, że zawiera on tylko odniesienie do jednego z naszych zminimalizowanych i oznaczonych plików. Dzieje się tak, że każde zadanie zapisuje osobny plik manifestu, a jedno z nich w końcu nadpisuje drugie. Będziemy musieli zmodyfikować zadania za pomocą dodatkowych parametrów, które poinstruują ich, aby poszukali istniejącego pliku manifestu i dołączyli do niego nowe dane, jeśli istnieje. Składnia jest nieco skomplikowana, więc spójrzmy, jak powinien wyglądać kod, a następnie przejrzyjmy go:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('default', ['pack-js', 'pack-css']);

Najpierw przesyłamy dane wyjściowe do rev.manifest() . Tworzy to oznaczone pliki zamiast plików, które mieliśmy wcześniej. Podajemy żądaną ścieżkę naszego rev-manifest.json i rev.manifest() , aby scalił się z istniejącym plikiem, jeśli istnieje. Następnie mówimy gulpowi, aby zapisał manifest do bieżącego katalogu, którym w tym momencie będzie public/build. Problem ze ścieżką jest spowodowany błędem, który jest szczegółowo omówiony w serwisie GitHub.

Mamy teraz automatyczną minifikację, otagowane pliki i plik manifestu. Wszystko to pozwoli nam szybciej dostarczać pliki do użytkownika i niszczyć ich pamięć podręczną za każdym razem, gdy wprowadzamy nasze modyfikacje. Pozostały jednak tylko dwa problemy.

Pierwszy problem polega na tym, że jeśli dokonamy jakichkolwiek modyfikacji w naszych plikach źródłowych, otrzymamy nowo otagowane pliki, ale stare również tam pozostaną. Potrzebujemy jakiegoś sposobu na automatyczne usuwanie starych, zminifikowanych plików. Rozwiążmy ten problem za pomocą wtyczki, która pozwoli nam usunąć pliki:

 npm install --save-dev del

Będziemy tego wymagać w naszym kodzie i zdefiniujemy dwa nowe zadania, po jednym dla każdego typu pliku źródłowego:

 var del = require('del'); gulp.task('clean-js', function () { return del([ 'public/build/js/*.js' ]); }); gulp.task('clean-css', function () { return del([ 'public/build/css/*.css' ]); });

Następnie upewnimy się, że nowe zadanie zakończy się przed naszymi dwoma głównymi zadaniami:

 gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {

Jeśli po tej modyfikacji ponownie uruchomimy gulp , będziemy mieli tylko najnowsze zminifikowane pliki.

Drugi problem polega na tym, że nie chcemy łykać łyka za każdym razem, gdy wprowadzamy zmianę. Aby rozwiązać ten problem, musimy zdefiniować zadanie obserwatora:

 gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });

Zmienimy również definicję naszego domyślnego zadania:

 gulp.task('default', ['watch']);

Jeśli teraz uruchomimy gulp z wiersza poleceń, przekonamy się, że nie buduje on już niczego po wywołaniu. Dzieje się tak, ponieważ teraz wywołuje zadanie obserwatora, które będzie obserwowało nasze pliki źródłowe pod kątem wszelkich zmian i kompiluje tylko wtedy, gdy wykryje zmianę. Jeśli spróbujemy zmienić którykolwiek z naszych plików źródłowych, a następnie ponownie spojrzymy na naszą konsolę, zobaczymy, że zadania pack-js i pack-css uruchamiają się automatycznie wraz z ich zależnościami.

Teraz wszystko, co musimy zrobić, to załadować plik manifest.json do naszej aplikacji i pobrać z niego otagowane nazwy plików. Sposób, w jaki to robimy, zależy od naszego konkretnego zaplecza językowego i stosu technologicznego, a wdrożenie byłoby dość trywialne, więc nie będziemy tego szczegółowo omawiać. Jednak ogólna idea jest taka, że ​​możemy załadować manifest do tablicy lub obiektu, a następnie zdefiniować funkcję pomocniczą, która pozwoli nam wywoływać wersjonowane zasoby z naszych szablonów w sposób podobny do następującego:

 gulp('bundle.js')

Gdy to zrobimy, nie będziemy już musieli martwić się o zmiany znaczników w naszych nazwach plików i będziemy mogli skupić się na pisaniu kodu wysokiej jakości.

Ostateczny kod źródłowy tego artykułu wraz z kilkoma przykładowymi zasobami można znaleźć w tym repozytorium GitHub.

Wniosek

W tym artykule omówiliśmy, jak wdrożyć automatyzację opartą na Gulp w naszym procesie kompilacji. Mam nadzieję, że okaże się to dla Ciebie pomocne i pozwoli rozwijać bardziej wyrafinowane procesy budowania we własnych aplikacjach.

Należy pamiętać, że Gulp jest tylko jednym z narzędzi, które można w tym celu wykorzystać, a istnieje wiele innych, takich jak Grunt, Browserify i Webpack. Różnią się one celami i zakresem problemów, które mogą rozwiązać. Niektóre mogą rozwiązywać problemy, których Gulp nie może, takie jak łączenie modułów JavaScript z zależnościami, które można załadować na żądanie. Nazywa się to „dzieleniem kodu” i jest to ulepszenie w stosunku do idei udostępniania jednego dużego pliku ze wszystkimi częściami naszego programu na każdej stronie. Narzędzia te są dość wyrafinowane, ale mogą zostać omówione w przyszłości. W następnym poście omówimy, jak zautomatyzować wdrażanie naszej aplikacji.

Powiązane: Łyk pod maską: tworzenie narzędzia do automatyzacji zadań opartego na strumieniu