Bootstrap3のソースコードをGruntでコンパイルする

# 概要

Bootstrap3のソースコードをGruntでコンパイルする手順のまとめです。

**環境**

* Windows7 (64bit)
* Node.js v4.2.2
* grunt-cli v0.1.13

**参考**

* [grunt] (http://gruntjs.com/)
* [grunt - github] (https://github.com/gruntjs)
* [Bootstrap] (http://getbootstrap.com/)


## 準備

### プロジェクト

プロジェクトのディレクトリを作成します。
ディレクトリ名は`grunt-example`としました。

```bat
> mkdir grunt-example
> cd grunt-example
```


### grunt-cli

gruntをインストールします。

```bat:install
> npm install -g grunt-cli
```

```bat:version
> grunt --version
grunt-cli v0.1.13
```


### jekyll

一部のタスクの実行で必要になるjekyllをインストールします。

```bat:install
> gem install jekyll
```

```bat:version
> jekyll -v
jekyll 3.0.0
```


### bootstrap3

[Bootstrap] (http://getbootstrap.com/)よりSource codeのアーカイブファイルをダウンロードし、プロジェクトディレクトリ内に展開します。

ダウンロードファイル: bootstrap-3.3.5.zip

展開後のディレクトリ／ファイル構成（一部抜粋）は下記の通りです。

```text:tree
grunt-example
 └─bootstrap-3.3.5
    ├─dist
    │  ├─css
    │  ├─fonts
    │  └─js
    ├─docs
    │  ├─assets
    │  ├─dist
    │  └─example
    ├─fonts
    │  ├─glyphicons-halflings-regular.eot
    │  ├─glyphicons-halflings-regular.svg
    │  ├─glyphicons-halflings-regular.ttf
    │  ├─glyphicons-halflings-regular.woff
    │  └─glyphicons-halflings-regular.woff2
    ├─grunt
    ├─js
    │  ├─tests
    │  ├─affix.js
    │  ├─alert.js
    │  ├─button.js
    │  ├─...省略...
    │  ├─tooltip.js
    │  └─transition.js
    ├─less
    │  ├─mixins
    │  ├─alerts.less
    │  ├─badges.less
    │  ├─bootstrap.less
    │  ├─...省略...
    │  ├─variables.less
    │  └─wells.less
    ├─bower.json
    ├─composer.json
    ├─Gruntfile.js
    ├─package.js
    └─package.json
```

依存パッケージやgrunt contrib pluginをインストールします。

```bat:install
> cd bootstrap-3.3.5
> npm install
```

## コンパイル

下記のようにタスクを実行すると(タスクを明示しない場合はdefaultタスクが実行されます)、Gruntfile.jsに定義されたタスクに従ってlessのコンパイルやjs、cssの圧縮化などが行われます。

```bat:grunt
> grunt
```

タスクの実行後はdistディレクトリに結果が出力されます。

* dist/cssディレクトリには、less/*.lessをコンパイルしたcssファイルが格納されます。
* dist/fontsディレクトリには、fonts/*のファイルがコピーされます。
* dist/jsディレクトリには、js/*.jsを連結したjsファイルが格納されます。

```text:tree
grunt-example
 └─bootstrap-3.3.5
    ├─dist
    │  ├─css
    │  │  ├─bootstrap-theme.css
    │  │  ├─bootstrap-theme.css.map
    │  │  ├─bootstrap-theme.min.css
    │  │  ├─bootstrap.css
    │  │  ├─bootstrap.css.map
    │  │  └─bootstrap.min.css
    │  ├─fonts
    │  │  ├─glyphicons-halflings-regular.eot
    │  │  ├─glyphicons-halflings-regular.svg
    │  │  ├─glyphicons-halflings-regular.ttf
    │  │  ├─glyphicons-halflings-regular.woff
    │  │  └─glyphicons-halflings-regular.woff2
    │  └─js
    │     ├─bootstrap.js
    │     ├─bootstrap.min.js
    │     └─npm.js
    │
    ...省略...
```


## タスク

bootstrap3のGruntfile.jsで定義されている主なタスクについて調べました。

### default

defaultタスクは下記の3つのタスクのAliasタスクになります。

```js:Gruntfile.js
// Default task.
grunt.registerTask('default', ['clean:dist', 'copy:fonts', 'test']);
```

clean:distタスクは、distディレクトリを削除します。

```js:clean
clean: {
  dist: 'dist'
}
```

copy:fontsタスクは、fontsディレクトリをdistディレクトリへコピーします。

```js:copy
copy: {
  fonts: {
    expand: true,
    src: 'fonts/*',
    dest: 'dist/'
  }
}
```

testタスクは更に下記のタスクのAliasタスクです。

```js:test
// Test task.
var testSubtasks = [];
// Skip core tests if running a different subset of the test suite
if (runSubset('core') &&
    // Skip core tests if this is a Savage build
    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
  testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'csslint:dist', 'test-js', 'docs']);
}
// Skip HTML validation if running a different subset of the test suite
if (runSubset('validate-html') &&
    // Skip HTML5 validator on Travis when [skip validator] is in the commit message
    isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
  testSubtasks.push('validate-html');
}
// Only run Sauce Labs tests if there's a Sauce access key
if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
    // Skip Sauce if running a different subset of the test suite
    runSubset('sauce-js-unit') &&
    // Skip Sauce on Travis when [skip sauce] is in the commit message
    isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
  testSubtasks.push('connect');
  testSubtasks.push('saucelabs-qunit');
}
grunt.registerTask('test', testSubtasks);
```

defaultタスクの実行

タスク名を省略するとdefaultタスクが実行されます。

```bat:default
> grunt
```

もしくは明示的にdefaultとタスク名を指定します。

```bat:default
> grunt default
```



### clean

cleanタスクは指定したディレクトリやファイルを削除します。
タスクの実行には[grunt-contrib-clean](https://github.com/gruntjs/grunt-contrib-clean) pluginが必要です。

> Clean files and folders

target

* dist
* docs

```js:Gruntfile.js
clean: {
  dist: 'dist',
  docs: 'docs/dist'
}
```

cleanタスクの実行

```bat:clean
> grunt clean
Running "clean:dist" (clean) task
>> 1 path cleaned.

Running "clean:docs" (clean) task
>> 1 path cleaned.

Done, without errors.
```

clean:distタスクの実行

```bat:dist
> grunt clean:dist
Running "clean:dist" (clean) task
>> 1 path cleaned.

Done, without errors.
```


### less

lessタスクは指定したlessファイルをコンパイルしてcssファイルを生成します。
タスクの実行には[grunt-contrib-less] (https://github.com/gruntjs/grunt-contrib-less) pluginが必要です。

> Compile LESS files to CSS

target

* compileCore
* compileTheme

```js:Gruntfile.js
less: {
  compileCore: {
    options: {
      strictMath: true,
      sourceMap: true,
      outputSourceFiles: true,
      sourceMapURL: '<%= pkg.name %>.css.map',
      sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
    },
    src: 'less/bootstrap.less',
    dest: 'dist/css/<%= pkg.name %>.css'
  },
  compileTheme: {
    options: {
      strictMath: true,
      sourceMap: true,
      outputSourceFiles: true,
      sourceMapURL: '<%= pkg.name %>-theme.css.map',
      sourceMapFilename: 'dist/css/<%= pkg.name %>-theme.css.map'
    },
    src: 'less/theme.less',
    dest: 'dist/css/<%= pkg.name %>-theme.css'
   }
}
```

lessタスクの定義中に出てくるテンプレート`<%= pkg.name %>`は、下記の処理によってpackage.jsonのnameプロパティの値になります。

```js:Gruntfile.js
// Project configuration.
grunt.initConfig({

  // Metadata.
  pkg: grunt.file.readJSON('package.json'),

  ...省略...
});
```

package.json

```json:package.json
{
  "name": "bootstrap",
  "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
  "version": "3.3.5",

  ...省略...
}
```


lessタスクの実行

```bat:less
> grunt less
Running "less:compileCore" (less) task
File dist/css/bootstrap.css.map created.
File dist/css/bootstrap.css created

Running "less:compileTheme" (less) task
File dist/css/bootstrap-theme.css.map created.
File dist/css/bootstrap-theme.css created

Done, without errors.
```


### cssmin

cssminタスクは指定したcssファイルを圧縮します。
タスクの実行には[grunt-contrib-cssmin] (https://github.com/gruntjs/grunt-contrib-cssmin) pluginが必要です。

> Minify CSS

target

* minifyCore
* minifyTheme
* docs

```js:Gruntfile.js
cssmin: {
  options: {
    // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
    //    and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
    compatibility: 'ie8',
    keepSpecialComments: '*',
    advanced: false
  },
  minifyCore: {
    src: 'dist/css/<%= pkg.name %>.css',
    dest: 'dist/css/<%= pkg.name %>.min.css'
  },
  minifyTheme: {
    src: 'dist/css/<%= pkg.name %>-theme.css',
    dest: 'dist/css/<%= pkg.name %>-theme.min.css'
  },
  docs: {
    src: [
      'docs/assets/css/src/pygments-manni.css',
      'docs/assets/css/src/docs.css'
    ],
    dest: 'docs/assets/css/docs.min.css'
  }
}
```

cssminタスクの実行

```bat:cssmin
> grunt cssmin
Running "cssmin:minifyCore" (cssmin) task
>> 1 file created. 146.57 kB → 121.72 kB

Running "cssmin:minifyTheme" (cssmin) task
>> 1 file created. 22.7 kB → 20.61 kB

Running "cssmin:docs" (cssmin) task
>> 1 file created. 34.28 kB → 22.36 kB

Done, without errors.
```


### uglify

uglifyタスクは指定したファイルを圧縮します。
タスクの実行には[grunt-contrib-uglify] (https://github.com/gruntjs/grunt-contrib-uglify) pluginが必要です。

> Minify files with UglifyJS

target

* core
* customize
* docsJs

```js:Gruntfile.js
uglify: {
  options: {
    compress: {
      warnings: false
    },
    mangle: true,
    preserveComments: 'some'
  },
  core: {
    src: '<%= concat.bootstrap.dest %>',
    dest: 'dist/js/<%= pkg.name %>.min.js'
  },
  customize: {
    src: configBridge.paths.customizerJs,
    dest: 'docs/assets/js/customize.min.js'
  },
  docsJs: {
    src: configBridge.paths.docsJs,
    dest: 'docs/assets/js/docs.min.js'
  }
}
```

coreターゲットのsrcプロパティの値にはテンプレート`<%= concat.bootstrap.dest %>`が使用されていますが

```js:core
core: {
 src: '<%= concat.bootstrap.dest %>',
 dest: 'dist/js/<%= pkg.name %>.min.js'
},
```

このテンプレートが指すのはconcat:bootstrapタスクのdestプロパティの値になります。
このため、uglify:coreタスクを実行する前にconcat:bootstrapタスクを実行しておく必要があります。

```js:concat
concat: {
  ...省略...
  bootstrap: {
    src: [
      ...省略...
    ],
    dest: 'dist/js/<%= pkg.name %>.js'
  }
},
```

uglifyタスクの実行

```bat:uglify
> grunt uglify
Running "uglify:core" (uglify) task
>> 1 file created.

Running "uglify:customize" (uglify) task
>> 1 file created.

Running "uglify:docsJs" (uglify) task
>> 1 file created.

Done, without errors.
```

uglify:coreタスクの実行
concat:bootstrapタスクの実行前(bootstrap.min.jsが無い状態)にuglify:coreタスクを実行した場合。

```bat:core
> grunt uglify:core
Running "uglify:core" (uglify) task
>> Destination dist/js/bootstrap.min.js not written because src files were empty.
>> No files created.

Done, without errors.
```


### concat

concatタスクは指定したファイルを連結します。
タスクの実行には[grunt-contrib-concat] (https://github.com/gruntjs/grunt-contrib-concat) pluginが必要です。

> Concatenate files.

target

* bootstrap

```js:Gruntfile.js
concat: {
  options: {
    banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>',
    stripBanners: false
  },
  bootstrap: {
    src: [
      'js/transition.js',
      'js/alert.js',
      'js/button.js',
      'js/carousel.js',
      'js/collapse.js',
      'js/dropdown.js',
      'js/modal.js',
      'js/tooltip.js',
      'js/popover.js',
      'js/scrollspy.js',
      'js/tab.js',
      'js/affix.js'
    ],
    dest: 'dist/js/<%= pkg.name %>.js'
  }
}
```

concatタスクの実行

```bat:concat
> grunt concat
Running "concat:bootstrap" (concat) task
File dist/js/bootstrap.js created.

Done, without errors.
```