AngularJS の Yeoman雛型に Application Cache を導入


AngularJS で HTML5 の Cache manifest ファイルを利用して静的リソースを Application Cache にキャッシュする、というのを試してみたので、メモがてら手順をここに残します。

ちなみに Application Cache については、このサイトが分かりやすかったです。
⇒[アプリケーション キャッシュを初めて使う](http://www.html5rocks.com/ja/tutorials/appcache/beginner/)

# 前提

- 開発環境 は Mac OS X (Mavericks)とします。
- Yeoman と Yeoman の generator-angular を使います。
	- もしまだ入ってなければ、次のようにセットアップしてください。

		```
		# npm install -g yo grunt-cli bower
		# npm install -g generator-angular
		```

# 手順

## ① AngularJSのジェネレータを実行

- コマンドラインにて、

	```shell
	$ yo anguler --minsafe
	```

- 下記のように聞かれるのですが、何でもいいので適当に選択します。

	![スクリーンショット 2014-06-13 11.53.31.png](https://qiita-image-store.s3.amazonaws.com/0/35733/c797261d-8f19-2000-c6d3-b9438735dfba.png "スクリーンショット 2014-06-13 11.53.31.png")

- 雛型の生成が終わったら、とりあえず動作確認します。コマンドラインにて、

	```shell
	$ grunt serve
	```

- 下記のように表示されたらOK。

	![スクリーンショット 2014-06-13 11.57.51.png](https://qiita-image-store.s3.amazonaws.com/0/35733/abee4e05-22d3-4d6e-d7d8-e17622ea5f45.png "スクリーンショット 2014-06-13 11.57.51.png")

## ② Application Cache の導入

- `index.html` と同じ階層に（どこでもいいのですが）、マニフェストファイルを新規に作成します。
	- 今回は、AngularJSの本体ファイルをキャッシュしてみます。
	- 【注意】本番稼働時にはリリースビルド後のファイル名を記載してください。

	```sample.appcache
	CACHE MANIFEST
	# TODO: ここにコメントやバージョン番号を書く
	
	CACHE:
	
	bower_components/angular/angular.js
	
	NETWORK:
	*
	
	FALLBACK:
	
	```

	※ `CACHE:` エリアは正規表現を使えないので、対象ファイルを全て記載する必要があります。
	※ `NETWORK:` エリアは逆にキャッシュしないファイルを指定します。全てのHTTPリクエストは `CACHE` か `NETWORK` に定義されている必要があるので、`*` を指定しておくのがセオリーらしいです。

- `.htaccess` にて、マニフェストファイルのHTTPレスポンスにコンテンツタイプ `text/cache-manifest` が設定されていることを確認します。

	```apache:.htaccess（念のため確認）
	AddType text/cache-manifest      appcache manifest
	```

- `index.html` の `html` タグ部分で、先ほど作成したマニフェストファイルを指定します。

	```html:index.html
	<!--[if gt IE 8]><!--> <html class="no-js" 	manifest="sample.appcache"> <!--<![endif]-->
	```

	※マニフェストファイルを指定したファイル（今回でいうとこの `index.html`）もキャッシュ対象になる、という仕様のようです。

- iOS の Safari？では、ブラウザがマニフェストファイルの更新を検知しない仕様らしいので、`head` タグに次のように JavaScript を仕込んで更新を検知するようにします。
	- でないと、永遠に端末側のキャッシュが更新されないことになります。
	- でも、私の端末（iOS 7.1.1）の Safari では、スクリプトを組まなくても、起動毎にマニフェストファイルの更新を確認してました。

	```javascript:index.html
	<script>
	  window.addEventListener('load', function(e) {
	    window.applicationCache.addEventListener('updateready', function(e) {
	      if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
	        window.applicationCache.swapCache();
	        window.location.reload();
	      }
	    }, false);
	    if (navigator.onLine) {
	      window.applicationCache.update();
	    }
	    window.addEventListener("online", function() {
	      window.applicationCache.update();
	    }, false);
	  }, false);
	</script>
	```

- 必須ではないですが、Gruntのタスクも修正して、マニフェストファイルを修正したらブラウザをリロードするようにします。

	```diff:Gruntfile.js
	  grunt.initConfig({
	
	    〜
	
	    watch: {
	      js: {
	        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
	        tasks: ['newer:jshint:all'],
	        options: {
	          livereload: true
	        }
	      },
	      jsTest: {
	        files: ['test/spec/{,*/}*.js'],
	        tasks: ['newer:jshint:test', 'karma']
	      },
	      styles: {
	        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
	        tasks: ['newer:copy:styles', 'autoprefixer']
	      },
	      gruntfile: {
	        files: ['Gruntfile.js']
	      },
	      livereload: {
	        options: {
	          livereload: '<%= connect.options.livereload %>'
	        },
	        files: [
	          '<%= yeoman.app %>/{,*/}*.html',
	          '.tmp/styles/{,*/}*.css',
	          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
	+          "<%= yeoman.app %>/sample.appcache"
	        ]
	      }
	    },
	```

- また、リリース向けにビルドした際、マニフェストファイルもリリース用ディレクトリにコピーされるようにします。

	```diff:Gruntfile.js
	  grunt.initConfig({
	
	    〜
	
	    copy: {
	      dist: {
	        files: [{
	          expand: true,
	          dot: true,
	          cwd: '<%= yeoman.app %>',
	          dest: '<%= yeoman.dist %>',
	          src: [
	            '*.{ico,png,txt}',
	            '.htaccess',
	            '*.html',
	            'views/{,*/}*.html',
	            'bower_components/**/*',
	            'images/{,*/}*.{webp}',
	            'fonts/*',
	+            'sample.appcache'
	          ]
	        }, {
	          expand: true,
	          cwd: '.tmp/images',
	          dest: '<%= yeoman.dist %>/images',
	          src: ['generated/*']
	        }]
	      },
	      styles: {
	        expand: true,
	        cwd: '<%= yeoman.app %>/styles',
	        dest: '.tmp/styles/',
	        src: '{,*/}*.css'
	      }
	    },
	```

## ③ 動作確認

- `$grunt serve` してローカルのWEBサーバを立ち上げ、Chrome の Developer Tools で各ファイルの HTTP リクエストの状態を確認します。参考までに、下記は Application Cache を導入する前の状態です。

	![a84fad72-b4ca-3f0f-8fbe-38c6dd3ac08d.png](https://qiita-image-store.s3.amazonaws.com/0/35733/38a42766-895a-97c6-01b1-2d7e0ababbaa.png "a84fad72-b4ca-3f0f-8fbe-38c6dd3ac08d.png")

- 下記は導入後です。マニフェストファイルで指定したファイルが、次のようにキャッシュから表示されていることが分かります。

	![96e54ed4-8018-07c3-0d15-443df948b252.png](https://qiita-image-store.s3.amazonaws.com/0/35733/ebbb08ac-e6dd-f6d4-392f-61a575021869.png "96e54ed4-8018-07c3-0d15-443df948b252.png")

- また Chrome では、次のURLをブラウザに入力することで、Application Cache の内容を確認することが出来ます。

	`chrome://appcache-internals/`

	![スクリーンショット 2014-06-13 18.18.57.png](https://qiita-image-store.s3.amazonaws.com/0/35733/4633bcca-15b1-d7ff-e38e-336bb899523b.png "スクリーンショット 2014-06-13 18.18.57.png")

# ほか

- 私のAndorid端末（GALAXY S II WiMAX：ISW11SC）の標準ブラウザ（Android 4.0.4）では、`window.applicationCache.`系や `window.location.reload()`のスクリプトが動作しませんでした.. ブラウザ依存があるのかなあ。
- 今回のソースはGitHubに置いておきます。
	- https://github.com/hkusu/AngularJS_appcache_demo