Gulp:サイトの速度を最大化するためのWeb開発者の秘密兵器
公開: 2022-03-11私たちの多くは、さまざまなサービスを一般に提供する本番環境で使用されるWebベースのプロジェクトを処理する必要があります。 このようなプロジェクトを扱うときは、コードをすばやくビルドしてデプロイできることが重要です。 特にプロセスが繰り返される場合、何かをすばやく実行するとエラーが発生することがよくあります。したがって、そのようなプロセスを可能な限り自動化することをお勧めします。
この投稿では、このような自動化を実現するためのツールの一部となるツールについて説明します。 このツールはGulp.jsと呼ばれるnpmパッケージです。 この投稿で使用されている基本的なGulp.jsの用語を理解するには、Toptalの仲間の開発者の1人であるAntoniosMinasによって以前にブログで公開された「Gulpを使用したJavaScript自動化の概要」を参照してください。 npm環境はこの投稿全体でパッケージをインストールするために広く使用されているため、基本的な知識があることを前提としています。
フロントエンド資産の提供
続行する前に、Gulp.jsで解決できる問題の概要を把握するために、少し前に戻りましょう。 多くのWebベースのプロジェクトは、Webページにさまざまな機能を提供するためにクライアントに提供されるフロントエンドJavaScriptファイルを備えています。 通常、クライアントにも提供されるCSSスタイルシートのセットもあります。 WebサイトまたはWebアプリケーションのソースコードを見ると、次のようなコードが表示されることがあります。
<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>
このコードにはいくつかの問題があります。 2つの別々のCSSスタイルシートと4つの別々のJavaScriptファイルへの参照があります。 これは、サーバーがサーバーに対して合計6つの要求を行う必要があり、ページの準備が整う前に、各要求がリソースを個別にロードする必要があることを意味します。 HTTP / 2は並列処理とヘッダー圧縮を導入するため、これはHTTP / 2の問題ではありませんが、それでも問題です。 このページの読み込みに必要なトラフィックの総量が増加し、ファイルの読み込みに時間がかかるため、ユーザーエクスペリエンスの品質が低下します。 HTTP 1.1の場合、ネットワークを占有し、使用可能な要求チャネルの数も減らします。 CSSファイルとJavaScriptファイルをそれぞれ1つのバンドルにまとめたほうがはるかに良かったでしょう。 そうすれば、合計2つのリクエストしかありません。 また、これらのファイルの縮小バージョンを提供することもできたはずです。これらのファイルは通常、元のファイルよりもはるかに小さいものです。 アセットのいずれかがキャッシュされている場合、Webアプリケーションも破損する可能性があり、クライアントは古いバージョンを受け取ります。
これらの問題のいくつかを解決するための基本的なアプローチの1つは、テキストエディターを使用して各タイプのアセットを手動でバンドルに結合し、その結果をhttp://jscompress.com/などのミニファイアサービスで実行することです。 これは、開発プロセス中に継続的に行うのは非常に面倒です。 わずかですが疑わしい改善は、GitHubで利用可能なパッケージの1つを使用して、独自のミニファイアサーバーをホストすることです。 次に、次のように見えることを行うことができます。
<script src="min/f=js/site.js,js/module1.js"></script>
これにより、縮小されたファイルがクライアントに提供されますが、キャッシュの問題は解決されません。 また、サーバーは基本的にすべてのリクエストですべてのソースファイルを繰り返し連結して縮小する必要があるため、サーバーに追加の負荷がかかります。
Gulp.jsによる自動化
確かに、これら2つのアプローチのどちらよりもうまくいくことができます。 私たちが本当に望んでいるのは、バンドルを自動化し、それをプロジェクトのビルドフェーズに含めることです。 最終的には、すでに縮小されて提供できる状態になっている、事前に構築されたアセットバンドルを作成したいと考えています。 また、リクエストごとにバンドルされたアセットの最新バージョンをクライアントに受信させる必要がありますが、可能であればキャッシュを活用したいと考えています。 幸いなことに、Gulp.jsはそれを処理できます。 この記事の残りの部分では、Gulp.jsの機能を活用してファイルを連結および縮小するソリューションを構築します。 また、更新がある場合は、プラグインを使用してキャッシュを無効にします。
この例では、次のディレクトリとファイル構造を作成します。
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
gulpfile.jsファイルは、Gulpが実行するタスクを定義する場所です。 package.json
は、アプリケーションのパッケージを定義し、インストールする依存関係を追跡するためにnpmによって使用されます。 パブリックディレクトリは、Webに面するように構成する必要があるものです。 アセットディレクトリは、ソースファイルを保存する場所です。 プロジェクトでGulpを使用するには、npmを介してインストールし、プロジェクトの開発者依存関係として保存する必要があります。 また、Gulp用のconcat
プラグインから始めたいと思います。これにより、複数のファイルを1つに連結できます。
これら2つのアイテムをインストールするには、次のコマンドを実行します。
npm install --save-dev gulp gulp-concat
次に、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']);
ここでは、gulpライブラリとそのconcatプラグインをロードしています。 次に、3つのタスクを定義します。
最初のタスク( pack-js
)は、複数のJavaScriptソースファイルを1つのバンドルに圧縮する手順を定義します。 指定された順序でグロブされ、読み取られ、連結されるソースファイルを一覧表示します。 これをconcatプラグインにパイプして、 bundle.js
という1つの最終ファイルを取得します。 最後に、ファイルをpublic/build/js
に書き込むようにgulpに指示します。
2番目のタスク( pack-css
)は、上記と同じことを行いますが、CSSスタイルシートを対象としています。 連結された出力をstylesheet.css
としてpublic/build/css
に保存するようにGulpに指示します。
3番目のタスク( default
)は、引数なしでGulpを呼び出したときに実行されるタスクです。 2番目のパラメーターでは、デフォルトのタスクの実行時に実行する他のタスクのリストを渡します。
通常使用しているソースコードエディタを使用して、このコードをgulpfile.jsに貼り付けてから、ファイルをアプリケーションルートに保存しましょう。
次に、コマンドラインを開いて実行します。
gulp
このコマンドを実行した後にファイルを見ると、 public/build/js/bundle.js
とpublic/build/css/stylesheet.css
の2つの新しいファイルが見つかります。 これらはソースファイルの連結であり、元の問題の一部を解決します。 ただし、それらは縮小されておらず、キャッシュの無効化はまだありません。 自動縮小を追加しましょう。
構築された資産の最適化
2つの新しいプラグインが必要になります。 それらを追加するには、次のコマンドを実行します。
npm install --save-dev gulp-clean-css gulp-minify
最初のプラグインはCSSを縮小するためのもので、2番目のプラグインはJavaScriptを縮小するためのものです。 1つ目はclean-cssパッケージを使用し、2つ目はUglifyJS2パッケージを使用します。 最初に、これら2つのパッケージをgulpfile.jsにロードします。
var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');
次に、出力をディスクに書き込む直前に、タスクでそれらを使用する必要があります。
.pipe(minify()) .pipe(cleanCss())
gulpfile.jsは次のようになります。
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']);
もう一度gulpを実行してみましょう。 ファイルstylesheet.css
が縮小された形式で保存され、ファイルbundle.js
はそのまま保存されていることがわかります。 縮小されたbundle-min.jsもあることに気付くでしょう。 縮小されたファイルのみが必要であり、 bundle.js
として保存する必要があるため、追加のパラメーターを使用してコードを変更します。

.pipe(minify({ ext:{ min:'.js' }, noSource: true }))
gulp-minifyプラグインのドキュメント(https://www.npmjs.com/package/gulp-minify)に従って、これにより縮小バージョンの目的の名前が設定され、元のソースを含むバージョンを作成しないようにプラグインに指示されます。 ビルドディレクトリの内容を削除し、コマンドラインからgulpを再度実行すると、縮小されたファイルが2つだけになります。 ビルドプロセスの縮小フェーズの実装が完了しました。
キャッシュバスティング
次に、キャッシュバスティングを追加し、そのためのプラグインをインストールする必要があります。
npm install --save-dev gulp-rev
そして、gulpファイルでそれを要求します:
var rev = require('gulp-rev');
プラグインの使用は少し注意が必要です。 最初に、縮小された出力をプラグインにパイプする必要があります。 次に、結果をディスクに書き込んだ後、プラグインを再度呼び出す必要があります。 プラグインは、一意のハッシュでタグ付けされるようにファイルの名前を変更し、マニフェストファイルも作成します。 マニフェストファイルは、アプリケーションがHTMLコードで参照する必要のある最新のファイル名を決定するために使用できるマップです。 gulpファイルを変更すると、次のようになります。
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']);
ビルドディレクトリの内容を削除して、もう一度gulpを実行してみましょう。 これで、各ファイル名にハッシュタグが付けられた2つのファイルと、 public/build
に保存されたmanifest.jsonがあることがわかります。 マニフェストファイルを開くと、縮小およびタグ付けされたファイルの1つへの参照のみが含まれていることがわかります。 何が起こっているのかというと、各タスクが別々のマニフェストファイルを書き込み、そのうちの1つがもう1つを上書きしてしまうということです。 既存のマニフェストファイルを検索し、新しいデータが存在する場合はそのファイルにマージするように指示する追加のパラメーターを使用して、タスクを変更する必要があります。 そのための構文は少し複雑なので、コードがどのように見えるかを見てから、それを調べてみましょう。
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']);
最初に出力をrev.manifest()
にパイプします。 これにより、以前のファイルの代わりにタグ付きファイルが作成されます。 rev-manifest.json
の目的のパスを提供し、 rev.manifest()
に既存のファイル(存在する場合)にマージするように指示しています。 次に、マニフェストを現在のディレクトリに書き込むようにgulpに指示します。このディレクトリは、その時点でpublic/buildになります。 パスの問題は、GitHubで詳細に説明されているバグが原因です。
これで、自動ミニファイ、タグ付きファイル、およびマニフェストファイルができました。 これらすべてにより、ファイルをより迅速にユーザーに配信し、変更を加えるたびにキャッシュを無効にすることができます。 ただし、残りの問題は2つだけです。
最初の問題は、ソースファイルに変更を加えると、新しくタグ付けされたファイルが取得されますが、古いファイルもそこに残るということです。 古い縮小ファイルを自動的に削除する方法が必要です。 ファイルを削除できるプラグインを使用して、この問題を解決しましょう。
npm install --save-dev del
コードでそれを要求し、ソースファイルのタイプごとに1つずつ、2つの新しいタスクを定義します。
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' ]); });
次に、2つの主要なタスクの前に、新しいタスクの実行が終了することを確認します。
gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {
この変更後にgulp
を再度実行すると、最新の縮小ファイルのみが作成されます。
2番目の問題は、変更を加えるたびにgulpを実行し続けたくないということです。 これを解決するには、ウォッチャータスクを定義する必要があります。
gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });
デフォルトのタスクの定義も変更します。
gulp.task('default', ['watch']);
コマンドラインからgulpを実行すると、呼び出し時に何もビルドされなくなっていることがわかります。 これは、ソースファイルの変更を監視し、変更を検出した場合にのみビルドするウォッチャータスクを呼び出すためです。 ソースファイルのいずれかを変更してからコンソールをもう一度見ると、 pack-js
タスクとpack-css
タスクが依存関係とともに自動的に実行されていることがわかります。
これで、アプリケーションにmanifest.jsonファイルをロードし、そこからタグ付きファイル名を取得するだけです。 それをどのように行うかは、特定のバックエンド言語とテクノロジースタックに依存し、実装するのは非常に簡単なので、詳細には説明しません。 ただし、一般的な考え方は、マニフェストを配列またはオブジェクトにロードしてから、次のような方法でテンプレートからバージョン管理されたアセットを呼び出すことができるヘルパー関数を定義できるというものです。
gulp('bundle.js')
そうすれば、ファイル名のタグの変更について心配する必要がなくなり、高品質のコードの記述に集中できるようになります。
この記事の最終的なソースコードは、いくつかのサンプルアセットとともに、このGitHubリポジトリにあります。
結論
この記事では、ビルドプロセスにGulpベースの自動化を実装する方法について説明しました。 これがお役に立てば幸いです。また、独自のアプリケーションでより洗練されたビルドプロセスを開発できるようになることを願っています。
Gulpはこの目的に使用できるツールの1つにすぎず、Grunt、Browserify、Webpackなど他にも多くのツールがあることに注意してください。 それらは、目的や解決できる問題の範囲が異なります。 JavaScriptモジュールをオンデマンドでロードできる依存関係にバンドルするなど、Gulpでは解決できない問題を解決できるものもあります。 これは「コード分割」と呼ばれ、プログラムのすべての部分をすべてのページに含む1つの大きなファイルを提供するという考え方を改善したものです。 これらのツールは非常に洗練されていますが、将来的にカバーされる可能性があります。 次の投稿では、アプリケーションのデプロイを自動化する方法について説明します。