tamuraです。

先日、社内勉強会で「なぜフロントエンドの開発にビルドが必要なのか?」という質問が出てうまく回答できなかったので、フロントエンド開発でなにを行っているか少しまとめてみました。

まあ一言で言うと開発効率向上のためです。


雛形作成

最近はフロントエンド側をAngularJSで作ることが多いのでYeomanを使って雛形を作っています。 その際にgenerator-angularでアプリケーションの雛形を作ります。

$ mkdir sample
$ cd sample
$ yo angular sample

フロントエンド用のライブラリのダウンロード

Bowerはフロントエンド用のパッケージマネージャです。
例えばlodashというライブラリを使いたい場合、 以前の私であればサイトからリリースファイルをダウンロードしてローカルに配置してということをやっていましたが、 Bowerを使うと

$ bower install lodash --save

だけで済みます。

これでbower_componentsというディレクトリにダウンロードされるので、それを読み込むだけです。

Bowerでダウンロードしたファイルの自動反映

前述のBower等でインストールしたファイルはbower_componentsディレクトリに格納されます。 これを手でindex.htmlに反映させても良いのですが、wiredepを使うことで自動でindex.htmlに反映してくれます。

Gruntfile.js

  wiredep {
    options: {
      cwd: '<%= yeoman.app %>'
    },
    app: {
      src: ['<%= yeoman.app %>/index.html'],
      ignorePath: /\\.\\.\\//
    }
  }

index.html

<html>  
<head>  
  <!-- bower:css -->
  <!-- endbower -->
</head>  
<body>  
  <!-- bower:js -->
  <!-- endbower -->
</body>  
</html>  

この状態で

$ grunt wiredep

と打つことでindex.htmlにBowerでインストールしたライブラリが反映されます。

<html>  
<head>  
  <!-- bower:css -->
  <!-- endbower -->
</head>  
<body>  
  <!-- bower:js -->
  <script src="bower_components/lodash/lodash.js"></script>
  <!-- endbower -->
</body>  
</html>  

この例はJavaScriptだけですが、CSSがある場合はCSSの読み込みも反映されます。

自作のJS/CSSの自動反映

JSやCSSが増える都度手作業でファイルをindex.html等に追加するのは面倒です。
grunt-asset-injectorを使うことでwiredepのようにファイルを自動で追加してくれます。

Gruntfile.js

  injector: {
    options: {
    },
    scripts: {
      options: {
        transform: function (filePath) {
          filePath = filePath.replace('/app\\/app/', 'app/');
          filePath = filePath.replace('/app\\/components/', 'components/');
          filePath = filePath.replace('/.tmp/', '');
          return '<script src="' + filePath + '"></script>';
        },
        starttag: '<!-- injector:js -->',
        endtag: '<!-- endinjector -->'
      },
      files: {
        '<%= yeoman.app %>/index.html': [
          ['{.tmp,<%= yeoman.app %>/{app,components}/{,*/}*.js',
           '!{.tmp,<%= yeoman.app %>/{app,components}/{,*/}*.spec.js',
           '!{.tmp,<%= yeoman.app %>/{app,components}/{,*/}*.mock.js']
        ]
      }
    },
    css: {
      options: {
        transform: function (filePath) {
          filePath = filePath.replace('/app\\/app/', 'app/');
          filePath = filePath.replace('/app\\/components/', 'components/');
          filePath = filePath.replace('/.tmp/', '');
          return '<link rel="stylesheet" href="' + filePath + '">';
        },
        starttag: '<!-- injector:css -->',
        endtag: '<!-- endinjector -->'
      },
      files : {
        '<%= yeoman.app %>/index.html': [
          '{.tmp,<%= yeoman.app %>}/{app,components}/{,*/}*.css'
        ]
      }
    }
  }

index.html

<html>  
<head>  
  <!-- bower:css -->
  <!-- endbower -->
  <!-- injector:css -->
  <!-- endinjector -->
</head>  
<body>  
  <!-- bower:js -->
  <!-- endbower -->
  <!-- injector:js -->
  <!-- endinjector -->
</body>  
</html>  

この状態で

$ grunt injector

と打つことでindex.htmlに対して、追加したJS/CSSファイルが反映されます。

<html>  
<head>  
  <!-- bower:css -->
  <!-- endbower -->
  <!-- injector:css -->
  <link rel="stylesheet" href="app/app.css">
  <!-- endinjector -->
</head>  
<body>  
  <!-- bower:js -->
  <!-- endbower -->
  <!-- injector:js -->
  <script src="app/app.js">
  <!-- endinjector -->
</body>  
</html>  

複数のファイルの結合

AngularJSを使ったアプリはJSファイルが多くなりがちです。
ファイル数が増えるとその分サーバへのアクセスが増え、結果的に遅くなります。 開発中はそれでも問題はないのですが、本番環境で動かす際に問題となります。

そのため、grunt-useminで複数のファイルを結合します。

Gruntfile.js

  useminPrepare: {
    html: '<%= yeoman.app %>/index.html',
    options: {
      dest: '<%= yeoman.dist %>'
    }
  },
  usemin: {
    html: ['<%= yeoman.dist %>/{,*/}*html'],
    css: ['<%= yeoman.dist %>/{,*/}*.css'],
    js: ['<%= yeoman.dist %>/{,*}*.js'],
    options: {
      assetsDirs: [
        '<%= yeoman.dist %>',
        '<%= yeoman.dist %>/assets/images',
      ],
      patterns: {
        js: [
          [/(assets\\/images\\/.*?\\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
        ],
        css: [
          [/()/g, 'Replacing reference to FontFile']
        ]
      }
    }
  }
<html>  
  <head>
    <!-- build:css assets/css/vendor.css -->
      <!-- bower:css -->
      <!-- endbower -->
    <!-- endbuild -->

    <!-- build:css assets/css/app.css -->
      <!-- injector:css -->
      <!-- endinjector -->
    <!-- endbuild -->
  </head>
  <body>
  <!-- build:js assets/script/vendor.js -->
    <!-- bower:js -->
    <!-- endbower -->
  <!-- endbuild -->

  <!-- build:js assets/script/app.js -->
    <!-- injector:js -->
    <!-- endinjector -->
  <!-- endbuild -->
  </body>
</html>  

この状態からビルドを行うことで、index.htmlが更新されます。

<html>  
  <head>
    <link rel="stylesheet" href="assets/css/vendor.css"/>
    <link rel="stylesheet" href="assets/css/app.css"/>
  </head>
  <body>
    <script src="assets/script/vendor.js"></script>
    <script src="assets/script/app.js"></script>
  </body>
</html>  

その他

generator-angularで色々なタスクが登録されています。 index.htmlが変更されるため主に意識しているのは上記のタスクですが、 Gruntfile.jsに色々かいてあるので読んでみてください。

ちょっと紹介しきれませんでした。