Corredor

ウェブ、プログラミングの勉強メモ。

Wiredep・Gulp-Inject・Gulp-Useref で HTML ファイルからの CSS・JS 読み込みを自動化

Webフロントエンド ハイパフォーマンス チューニング

Webフロントエンド ハイパフォーマンス チューニング

HTML ファイルから必要な Bower コンポーネントや自分で作ってビルドしたファイル群を読み込ませたり、そのファイル構成を変更したりするのが中々だるい。

そこで、HTML に書く style 要素・script 要素を整理してくれる Gulp 関連ツールとして、Wiredep・Gulp-Inject・Gulp-Useref の3つを紹介する。

実際のコードはコチラ

今回紹介したパッケージとサンプルコードを利用し、AngularJS + Cordova なアプリで Appium を使った E2E テストを行うサンプルプロジェクトを作成した。以下を参考にしてほしい。

gulpfile.js はコチラ。

まずは3つのツールをインストールしておく

説明の前に、3つのツールと、Gulp のインストールをしておく。

# Gulp-Load-Plugins は require() の手間を省くため導入
$ npm i -D gulp gulp-load-plugins
$ npm i -D wiredep gulp-inject gulp-useref

あとは package.jsonscripts に、"gulp": "gulp" というタスクを追加しておけば、$ npm run gulp でローカルインストールした Gulp が使えるようになる。

Wiredep とは

Wiredep は、Wiredep を使うプロジェクトの bower.jsondependencies に記載されている Bower Components から main ファイルを取得し、そのファイルを読み込むタグを HTML 中に書き込んでくれるツール。

HTML 側の設定

HTML 中の、Wiredep に link 要素を注入して欲しい場所には

<!-- bower:css -->
<!-- endbower -->

というコメントを入れておき、script 要素を注入して欲しい場所には

<!-- bower:js -->
<!-- endbower -->

と書くだけだ。Wiredep によって、このコメント部分に link 要素や script 要素が挿入される。

main ファイルの定義が足りない場合は

Bower Components によっては、その Bower コンポーネントの bower.json に記載の main ファイルだけでは必要なファイルが読み込めない場合がある。その場合は別途、自プロジェクトの bower.jsonoverrides として読み込むべきファイルを定義し直してやると良い。

例えば Angular UI Bootstrap の bower.json は以下のようになっているが、これでは CSS ファイルが足りない。

// ./bower_components/angular-bootstrap/bower.json
"main": ["./ui-bootstrap-tpls.js"],

そこで、自身の bower.json に以下のようにして CSS ファイルも読み込むよう、main を書き換える設定を入れてやる。

// ./bower.json
"overrides": {
  "angular-bootstrap": {
    "main": [
      "./ui-bootstrap-csp.css",
      "./ui-bootstrap-tpls.js"
    ]
  }
}

Gulp での処理方法とその結果

Gulp スクリプトにおいては、以下のようにしてやる。

const gulp = require('gulp');

gulp.task('html-inject', () => {
  // Wiredep のストリームを取得する
  const wiredep = require('wiredep').stream;
  return gulp
    .src('./src/index.html')    // 元とする ./src/index.html を得る
    .pipe(wiredep())            // Wiredep にコメント部分を処理させる
    .pipe(gulp.dest('./www'));  // ./www/ 配下に加工した index.html を出力する
});

コレだけ。

こうして Wiredep に処理された HTML (./www/index.html) は、以下のようになっている。

<!-- bower:css -->
<link rel="stylesheet" href="../bower_components/angular-bootstrap/ui-bootstrap-csp.css" />
<!-- endbower -->

<!-- bower:js -->
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>
<!-- endbower -->

つまり、出力先ディレクトリから見た相対パスで bower_components ディレクトリ配下へのリンクを作っているだけだ。

これだけでは必要なファイルをビルド対象のディレクトリ (この場合 ./www/ 配下) に取り込めておらず、使い勝手が悪いので、あとで Gulp-Useref を使って調整する。

その前に Gulp-Inject の説明をする。

Gulp-Inject とは

Gulp-Inject というツールも、Wiredep とよく似ている。コチラは Bower Components に限らず、指定したファイルを読み込むための link 要素や script 要素を HTML 中に挿入してくれるというモノだ。

HTML 側の設定

HTML 側での書き方は Wiredep と同じくコメントで記載する。link 要素を出力したい場所には

<!-- inject:css -->
<!-- endinject -->

と記載し、script 要素を出力したい場所には

<!-- inject:js -->
<!-- endinject -->

と記載する。

Gulp での処理方法とその結果

今回はこのコメント部分に、別タスクで予めビルドしておいた CSS ファイルと JS ファイルを読み込むための設定をしてみる。

const gulp = require('gulp');
// Gulp-Load-Plugins を使って Gulp-Inject を $.inject() で使えるようにする
const $ = require('gulp-load-plugins')();

gulp.task('html-inject', () => {
  return gulp
    .src('./src/index.html')    // 元とする ./src/index.html を得る
    .pipe($.inject(             // Gulp-Inject を使用する
      gulp.src([
        './www/css/index.css',  // link 要素で出力したい CSS ファイルを指定
        './www/js/index.js'     // script 要素で出力したい JS ファイルを指定
      ]),
      { relative: true }        // 相対パス指定
    ))
    .pipe(gulp.dest('./www'));  // ./www/ 配下に加工した index.html を出力する
});

このように、挿入したい CSS・JS ファイルを配列で指定するだけ。

こうして生成された ./www/index.html 配下のようになっている。

<!-- inject:css -->
<link rel="stylesheet" href="../www/css/index.css">
<!-- endinject -->

<!-- inject:js -->
<script src="../www/js/index.js"></script>
<!-- endinject -->

確かに指定した ./www/css/index.css./www/js/index.js がリンクされている。しかし、なぜか相対パスの指定が ../www/ と冗長的な書き方になってしまっている。特段問題はないと思うが気持ち悪いので、これもこの後紹介する Gulp-Useref で修正する。

Wiredep と Gulp-Inject を同時に指定する

さて、今回は Wiredep と Gulp-Inject を使って、同じ ./src/index.html ファイルを編集したいので、1つのタスクにまとめてやる。難しいことはなく、IN と OUT (src()dest()) は同じなので、.pipe() を増やしてやるだけで良い。

const gulp = require('gulp');
const $ = require('gulp-load-plugins')();

gulp.task('html-inject', () => {
  const wiredep = require('wiredep').stream;
  return gulp
    .src('./src/index.html')    // IN
    .pipe(wiredep())            // Wiredep
    .pipe($.inject(             // Gulp-Inject
      gulp.src([
        './www/css/index.css',
        './www/js/index.js'
      ]),
      { relative: true }
    ))
    .pipe(gulp.dest('./www'));  // OUT
});

Gulp-Useref とは

Gulp-Useref は、HTML コメントで囲んだ範囲内の link 要素・script 要素のファイル群を1ファイルに結合して出力し、その1ファイルのみにリンクする link 要素・script 要素に HTML を書き換えてくれるものだ。

HTML 側の設定

実例で紹介しよう。まずは以下が ./src/index.html の加工前の状態だ (「▼」部分は説明書きで実際は記載していない)。入れ子の関係を分かりやすくするためインデントを付けているが、必須ではない。

<!-- build:css css/vendor.css -->
  ▼ Wiredep で Bower から CSS ファイルを注入する場所
  <!-- bower:css -->
  <!-- endbower -->
<!-- endbuild -->
    
<!-- build:css css/index.css -->
  ▼ Gulp-Inject で指定した CSS ファイルを注入する場所
  <!-- inject:css -->
  <!-- endinject -->
<!-- endbuild -->

<!-- build:js js/vendor.js -->
  ▼ Wiredep で Bower から JS ファイルを注入する場所
  <!-- bower:js -->
  <!-- endbower -->
<!-- endbuild -->

<!-- build:js js/index.js -->
  ▼ Gulp-Inject で指定した JS ファイルを注入する場所
  <!-- inject:js -->
  <!-- endinject -->
<!-- endbuild -->

ココに登場している、build:css とか build:js といったコメントが、Gulp-Useref が使うコメントだ。一番最初のコメントを例に取ると、

<!-- build:css css/vendor.css -->
<!-- endbuild -->

これで1セットだ。このコメントの範囲内にある CSS ファイル (link 要素) のみを抽出し、css/vendor.css という1つのファイルに結合する。ファイルを結合したら、css/vendor.css への link 要素のみをこのコメント部分に配置する、というワケだ。パスはその HTML ファイルから見ての相対パスになる。

Wiredep と Gulp-Inject を先に実行する

まずは先程紹介した、Wiredep + Gulp-Inject による注入タスクを実行し、./www/index.html を出力する。以下のようになっているはずだ。

<!-- build:css css/vendor.css -->
  <!-- Wiredep -->
  <!-- bower:css -->
  <link rel="stylesheet" href="../bower_components/angular-bootstrap/ui-bootstrap-csp.css" />
  <!-- endbower -->
<!-- endbuild -->

<!-- build:css css/index.css -->
  <!-- inject:css -->
  <link rel="stylesheet" href="../www/css/index.css">
  <!-- endinject -->
<!-- endbuild -->

<!-- build:js js/vendor.js -->
  <!-- bower:js -->
  <script src="../bower_components/angular/angular.js"></script>
  <script src="../bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
  <script src="../bower_components/angular-route/angular-route.js"></script>
  <!-- endbower -->
<!-- endbuild -->

<!-- build:js js/index.js -->
  <!-- inject:js -->
  <script src="../www/js/index.js"></script>
  <!-- endinject -->
<!-- endbuild -->

Wiredep と Gulp-Inject によって link 要素・script 要素が挿入されている。

Gulp-Useref による処理とその結果

次に、この ./www/index.html に対して Gulp-Useref を適用するタスクを作成する。

const gulp = require('gulp');
const $ = require('gulp-load-plugins')();

// ./www/index.html を生成する「html-inject」タスクを事前実行するタスクとして依存指定しておく
gulp.task('html-useref', ['html-inject'], () => {
  return gulp
    .src('./www/index.html')    // 前タスクで生成した ./www/index.html を取得する
    .pipe($.useref())           // Gulp-Useref を適用する
    .pipe(gulp.dest('./www'));  // ./www/ 配下に index.html を再度出力する
});

つまり、Wiredep と Gulp-Inject を行ったファイルを再度取得して Gulp-Useref を適用しているのだ。HTML ファイルの位置は ./www/ 配下になるので、ココからの相対パスだと <!-- build:css css/vendor.css --> という指定では ./www/css/vendor.css が生成される、という寸法になる。

これで ./www/index.html は以下のような記載になる。

<link rel="stylesheet" href="css/vendor.css">

<link rel="stylesheet" href="css/index.css">

<script src="js/vendor.js"></script>

<script src="js/index.js"></script>

Wiredep によって複数の link 要素・script 要素が列挙されていた部分は、./www/css/vendor.css./www/js/vendor.js という1つずつのファイルにまとめられた。

おまけに冗長的なパス指定になっていた index.cssindex.js があった link 要素・script 要素にも Gulp-Useref を噛ませることによって、パス指定を整形することができた。指定しているファイルが最初から1つずつしかないので、実質的にファイルの結合は発生していない。

コメントも消えて良い感じだ。

ファイルの結合と生成、そして HTML ファイル内の link 要素・script 要素の書き換えまでを一気にやってくれるのが Gulp-Useref なのだ。