gruntで簡単JSビルド
gruntってのは、JS/CSSをまとめたり、JS lint, Qunit, minifyできたりってのができるビルドツールです。
また、minifyなどの組み込みタスクの他に、RakeやAntと同じようにカスタムタスクを記述して実行することも可能です。
さらに、watchというので、対象のファイルを監視して、それをトリガーとしてタスクを実行することも可能です。(inofityみたいなやつ)
他の類似ツールと比べて、簡単に使えたのでメモとして残しときます。
環境
OS: MacOSX 10.7
実行環境: Node.js(Homebrew)
パッケージ管理: npm
手順
node環境のインストール
面倒なので(ry
この辺みてインストールしてください。
naveを使ったnode.jsインストールと、最近のnpmの使い方 - ラシウラ
Nodeとnpmのインストール - 自分の感受性くらい
MacOSX/homebrewでnodeを、パッケージ管理にnpmを使っているという想定でいきます。
gruntのインストール
$ npm install -g grunt
プロジェクトディレクトリセットアップ
$ cd project_dir $ npm init #<= package.jsonを作る(※あれば不要)
`npm init`で対話式にpackage.jsonを作成します。(自前で作成してもいいけど)
gruntは、package.jsonの内容を読み込むことができるので、Client側であっても作成しておくと便利です。
package.jsonのサンプル
{ author: "Satoshi Ohki <roothybrid7@gmail.com>", name: "grunt-sandbox", description: "Grunt sandbox project.", version: "0.0.0", repository: { type: "git", url: "git://github.com/roothybrid7/grunt-sandbox.git" }, engines: { node: "~0.6.14" }, noAnalyze: true, dependencies: { }, devDependencies: { grunt: "~0.3.2", grunt-sample: "~0.1.0" }, optionalDependencies: { } }
gruntセットアップ
本日の主役、下記のコマンドを実行し、何個か質問に答えるとカレントディレクトリにgrunt.jsが作成されます。
$ grunt init:gruntfile
gruntの設定
設定は、この中に記述します。
grunt.initConfig({ [...] });
ファイルをまとめる、minifyする
まとめたいファイルを、srcに指定して、destにまとめたファイル名を指定します。
'<json:package.json>'
は、jsonファイルをしている組み込みのディレクティブです。
destに、<%= pkg.name %>とありますが、package.jsonのnameを使うように指定しています。
metaのbannerで、package.jsonの値をいろいろ使用しています。ファイルのヘッダコメントとして使用しています。
'<config:concat.dist.dest>'
の箇所は、
このディレクティブを使えば、minifyでconcatに指定したのと同じファイルを簡単に指定できるようになります。
設定サンプル
grunt.initConfig({ pkg: '<json:package.json>', meta: { banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */' }, concat: { dist: { src: [ '<banner>', 'js/snip/**/*.js' ], dest: 'dist/<%= pkg.name %>.js' } }, min: { dist: { src: ['<banner>', '<config:concat.dist.dest>'], dest: 'dist/<%= pkg.name %>.min.js' } } });
concatやminは、別にdistという名前のプロパティでなくても任意のプロパティ名を使うことが可能です。しかも複数のプロパティを宣言できます。
例えば、ページ毎にまとめるファイルが異なる場合は、下記のような設定が可能です。
設定サンプル
grunt.initConfig({ [...] concat: { about: { src: [ '<banner>', '<file_strip_banner:js/snip/0-testscript.js>' ], dest: 'dist/<%= pkg.name %>-about.js' }, config: { src: [ '<banner>', 'js/snip/1-testscript.js', 'js/snip/2-testscript.js' ], dest: 'dist/<%= pkg.name %>-config.js' }, dashboard: { src: [ '<banner>', 'js/snip/3-testscript.js', 'js/snip/4-testscript.js', 'js/snip/5-testscript.js', 'js/snip/6-testscript.js', 'js/snip/7-testscript.js', 'js/snip/8-testscript.js', 'js/snip/9-testscript.js' ], dest: 'dist/<%= pkg.name %>-dashboard.js' }, }, min: { about: { src: ['<banner>', '<config:concat.about.dest>'], dest: 'dist/<%= pkg.name %>-about.min.js' }, config: { src: ['<banner>', '<config:concat.config.dest>'], dest: 'dist/<%= pkg.name %>-config.min.js' }, dashboard: { src: ['<banner>', '<config:concat.dashboard.dest>'], dest: 'dist/<%= pkg.name %>-dashboard.min.js' } }, [...] });
JS Lintの設定
JS lintを実行してくれるんですが、はっきりいってつかってません!!
なぜかといえば、jQueryの'$'とかunderscoreの'_'とかをErrorとして報告してきやがるからです。
下記のように設定すると、grunt.js自身と、js/snip/ディレクトリ以下のjavascriptファイルをすべてにJS lintを実行してくれます。
設定サンプル
grunt.initConfig({ lint: { files: [ 'grunt.js', 'js/snip/**/*.js' ] }, });
testの設定
qunit使っている人は使えばいいと思う。(これもつかってない)
下記のように設定すればいいらしいです。
設定サンプル
grunt.initConfig({ qunit: { files: ['test/**/*.html'] }, });
gruntタスク実行
defaultタスクの実行
grunt.jsのあるディレクトリで、`grunt`とだけ実行すると、defaultタスクが実行されます。(他のビルドツールと一緒)
$ grunt
defaultタスクは下記のように設定します。
実行したいタスクを、第2引数にスペース区切りで指定します。
// Default task. grunt.registerTask('default', 'concat min');
その他の組み込みタスクの実行
concat, min, lintなどの組み込みタスクを実行する場合、`grunt [task]`とtaskに実行したいタスクを指定します。
サンプル
$ grunt concat
タスクの一部だけ実行する場合には、grunt.jsで指定したプロパティ名を引数に指定します。
サンプル
$ grunt concat:about #<= concatタスクのaboutのみ実行される
デモ
100個のスクリプトをまとめて、さらにminifyするサンプルを書いてみました。
grunt.js設定ファイル
/*global module:false*/ module.exports = function(grunt) { var utils = grunt.utils, task = grunt.task, helper = grunt.helper, file = grunt.file, config = grunt.config, log = grunt.log, os = require('os').platform(); /** * Compare function. * * @param {*} a A comparable value1. * @param {*} b A comparable value2. * @return {number} The compare result. */ var compareFilename = function(a, b) { var numA = parseInt(a, 10), numB = parseInt(b, 10); return numA - numB; }; /** * Returns the rest of the elements in an array. * * @param {Array} arr An source array. * @param {number=} index The values of the array that index onward. * @return {Array} The rest array. */ grunt.registerHelper('rest', function(arr, index) { if (utils.kindOf(index) !== 'number') { index = 1; } return arr.slice(index); }); /** * Returns basename. * * @param {string} filepath A file path string. * @param {string=} suffix A remove suffix. * @return {string} basename string. */ grunt.registerHelper('basename', function(filepath, suffix) { var base = '', separator = os.match(/^win/) ? '\\' : '/'; if (filepath === '.' || filepath === separator) { base = filepath; } else { var entries = filepath && filepath.split(separator); while (entries.length) { if ((base = entries.pop())) break; } } if (utils.kindOf(suffix) === 'string' && suffix.length > 0) { base = base.replace(suffix, ''); } return base; }); /** * Returns sorted filelist. * * @param {string} patterns A file path wildcard. * @return {string} sorted filelist. */ grunt.registerHelper('sort_files', function(patterns) { var files = file.expandFiles(patterns); return sortedFiles = utils._.sortBy(files, function(file) { var basename = helper('basename', file); return parseInt(basename, 10); }); }); // Project configuration. grunt.initConfig({ pkg: '<json:package.json>', meta: { banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */' }, lint: { files: ['grunt.js', 'js/snip/**/*.js'] }, concat: { about: { src: ['<banner>', '<file_strip_banner:js/snip/0-testscript.js>'], dest: 'dist/<%= pkg.name %>-about.js' }, config: { src: ['<banner>', 'js/snip/1-testscript.js', 'js/snip/2-testscript.js'], dest: 'dist/<%= pkg.name %>-config.js' }, dashboard: { src: ['<banner>', 'js/snip/3-testscript.js', 'js/snip/4-testscript.js', 'js/snip/5-testscript.js', 'js/snip/6-testscript.js', 'js/snip/7-testscript.js', 'js/snip/8-testscript.js', 'js/snip/9-testscript.js'], dest: 'dist/<%= pkg.name %>-dashboard.js' }, dist: { src: ['<banner>', helper('sort_files', 'js/snip/**/*.js')], dest: 'dist/<%= pkg.name %>.js' }, distStrip: { src: ['<banner>', utils._.map(helper('sort_files', 'js/snip/**/*.js'), function(file) { return '<file_strip_banner:' + file + '>'; })], dest: 'dist/<%= pkg.name %>-strip.js' } }, min: { about: { src: ['<banner>', '<config:concat.about.dest>'], dest: 'dist/<%= pkg.name %>-about.min.js' }, config: { src: ['<banner>', '<config:concat.config.dest>'], dest: 'dist/<%= pkg.name %>-config.min.js' }, dashboard: { src: ['<banner>', '<config:concat.dashboard.dest>'], dest: 'dist/<%= pkg.name %>-dashboard.min.js' }, dist: { src: ['<banner>', '<config:concat.dist.dest>'], dest: 'dist/<%= pkg.name %>.min.js' } }, watch: { files: '<config:lint.files>', tasks: 'lint' }, jshint: { options: { curly: true, eqeqeq: true, immed: true, latedef: true, newcap: true, noarg: true, sub: true, undef: true, boss: true, eqnull: true, browser: true }, globals: {} }, uglify: {} }); // Default task. grunt.registerTask('default', 'concat min'); grunt.registerTask('sort_files_with_printout_test', 'print out sort files.', function() { log.writeln(this.name + ', ' + this.nameArgs); var sortedFiles = helper('sort_files', 'js/snip/**/*.js'); log.writeln(helper('concat', sortedFiles)); }); };
Cocoa/iOSライブラリのPodspecを修正して、CocoaPodsで依存関係の解決
みなさんCocoaPodsを活用してますか? CocoaPodsを使用するとライブラリの検索から依存関係の管理・インストールまで簡単に行うことができます。
CocoaPodsでライブラリを管理するためには、基本的にはライブラリの作者がpodspecというファイルを記述してCocoaPodsで使えるように必要があります。
今回使ってみようと思ったのは、QuickDialogというiOS HIG(ヒューマンインタフェースガイドライン)に準拠した設定画面のフォームが簡単に作れるものです。
このライブラリはCocoaPodsに対応してるんですが、依存するフレームワークの記述がないためインストール後にプロジェクトをビルドしようとすると以下のようにエラーになります。
ここで、手動でフレームワーク(この場合MapKit.framework)を追加してもいいんですが、CocoaPodsは自分専用に作成したリポジトリからpodspecを参照してライブラリをインストールすることが可能です。
なので新たに自分専用リポジトリを作成し、修正したpodspecを配置してビルドできるようにしてみます。
今回修正するpodspecの対象ライブラリ
- QuickDialog
Podspecファイルの作成とバージョン番号の変更
必要なもの
- 修正するPodspecファイル
- ライブラリ本体のリポジトリ
# ライブラリをcloneして、CocoaPodsでインストールしたバージョンのタグにHEADを移動 git clone git://github.com/escoz/QuickDialog.git cd QuickDialog git reset --hard 0.1 # CocoaPds/Specからpodspecファイルを取得 wget https://raw.github.com/CocoaPods/Specs/master/QuickDialog/0.1/QuickDialog.podspec # podspecに修正を加える edit QuickDialog.podspec # 以下を追加/ --- ../QuickDialog/QuickDialog.podspec.orig 2012-01-13 00:36:09.000000000 +0900 +++ QuickDialog/0.1/QuickDialog.podspec 2012-01-13 01:30:24.000000000 +0900 @@ -14,6 +14,7 @@ s.source_files = 'quickdialog' s.clean_paths = 'sample', '*.xc*', 'libQuickDialog', 'other' + s.framework = 'MapKit' s.requires_arc = true def s.post_install(target) # 誤りがないかチェック pod spec lint QuickDialog.podspec
自分用のCocoaPods用のpodspecを配置する
修正したpodspecファイルを自分のリポジトリにコミットして管理しますので
あらかじめ専用のリポジトリを作っておきます。
私の場合は、podspecsという名前で作成しました。
git clone git@github.com:roothybrid7/podspecs.git cd podspecs # ライブラリ名(podspecのs.name)/バージョン番号(s.version)のディレクトリ作成 mkdir -p QuickDialog/0.1 # 先ほど作成したpodspecファイルをコピー cp <QuickDialog.podspec> QuickDialog/0.1/ # commit and push. git add . && git ci -m 'Add QuickDialog podspec.' && git push origin master
CocoaPodsにpodspecのリポジトリを追加
CocoaPodsに作成したリポジトリを登録します。
# podspecのrepositoryを追加
pod repo add mypods git://github.com/roothybrid7/podspecs.git
Podfileの修正とアップデート
先ほど修正したpodspecファイルを参照するように、Podfileを修正します。
# podspecを配置したURLを記述する(raw) -dependency 'QuickDialog', '~> 0.1' +dependency 'QuickDialog', '~> 0.1', :podspec => 'https://raw.github.com/roothybrid7/podspecs/master/QuickDialog/0.1/QuickDialog.podspec'
修正したら `pod install` を実行して更新します。
これで再ビルドすると、MapKit.frameworkを手動で追加することなしに、正常にビルドすることができるようになりました。
終わりに
このようにライブラリのpodspecの記述がたりない場合、自分で自由に修正することが可能なので、自分専用リポジトリを作成して試してみてはいかがでしょうか
今回作成した自分専用podspecリポジトリ
参考
- CocoaPods ではじめる Objective-C ライブラリ管理 (2) - Watsonのメモ
- A-Liaison BLOG: CocoaPods に対応していないライブラリを集めた自分用リポジトリを作る方法
- CocoaPods/CHANGELOG.md at 6cf539af4882d2632a20ddc6be7b15d3d31e6c2e · CocoaPods/CocoaPods · GitHub
- QuickDialog - ESCOZ Inc
- roothybrid7/QuickDialog · GitHub
- Cocoaの日々: [Info] 設定画面構成ライブラリ - QuickDialog
build済みのGHUnitは実機上でテストできないので、自分でbuildしてください!!
GithubのDownloads · gabriel/gh-unit · GitHubにあるGHUnitIOS-0.4.33.zipを使って、実機上でテストを動かそうとしても下記のようにエラーになるので、
iOS: Linker could not find GHImageDiffView and YKUIImageViewControl -
GHUnit |
Google Groups
ここに書いてある通り、自分でbuildしてプロジェクトに放り込む
Issue #69: Framework missing symbols for ARMV7 · gabriel/gh-unit · GitHub
- git clone https://github.com/gabriel/gh-unit.git
- cd Project-iOS
- make
- Use the GHUnitIOS.framework under the build/Framework folder
流行のCocoaPods使ってみて、エラーになってはまった話(修正)
CocoaPodsを導入すると、iOS開発で使用するライブラリの導入管理が簡単になるとしって使ってみたんですが、いくつかはまったところがありました。
環境
環境はこんな感じ
最初にはまったところ
- 複数ターゲットでCocoaPodsで導入したライブラリを使用する場合、pod installをする前に必要なプロジェクトターゲットを作成する必要がある
- Header Search Pathが違う!! 一部のライブラリのヘッダファイルがHeadersディレクトリにシンボリックリンクされない!!
- テスト用static-libraryを作っても、デフォルト以外は自動でリンクはしてくれない
下のように、ターゲットが2つあるとして(アプリ用とテスト用)
後からターゲットを作成してしまうと、CocoaPodsでstatic-libraryがリンクされません。なので複数のターゲットを自動管理する場合は、pod installする前に必要なターゲットは作成しておくといいです。
2. Header Search Pathが違う!! 一部のライブラリのヘッダファイルがHeadersディレクトリにシンボリックリンクされない!!
*よく見たらHeadersディレクトリはありました!! なぜか全部のライブラリがコピーされない
> :exclusive => trueでほかのtargetで指定したライブラリのシンボリックリンクが作られないのは、バグっぽいので修正してpull request送りました。(2012/01/09)
> 上の暫定対策版をインストールできるようにしましたので、よろしければ、 CocoaPods 0.3.9 修正非公式版インストール — Gist からインストールしてみてください
3.のPodfileの記述に関係あるんですが、どうも targetブロックで:exclusive => trueを使ってしまうとtargetブロックの外で宣言しているライブラリのヘッダファイルがHeadersディレクトリにコピーされないみたいです。
- targetブロックで:exclusive => trueを使った場合は、targetブロックの外のライブラリのヘッダファイルがコピーされない
platform :ios dependency 'JSONKit', '~> 1.4' dependency 'AFNetworking', '~> 0.7' dependency 'BlocksKit', '~> 1.0' dependency 'NSLogger' # Testing framework for Test target. target :test, :exclusive => true do dependency 'LRMocky' end
- targetブロックで:exclude => trueを使わなければ全部コピーされる
platform :ios dependency 'JSONKit', '~> 1.4' dependency 'AFNetworking', '~> 0.7' dependency 'BlocksKit', '~> 1.0' dependency 'NSLogger' # Testing framework for Test target. target :test do dependency 'LRMocky' end
ということで、今のところtest用に追加のライブラリだけをパッケージングするのは難しそうです。
3. テスト用static-libraryを作っても、デフォルト以外は自動でリンクはしてくれない
下記のように、Podfileを記述したとしてtargetブロックに囲まれた方のstatic-libraryは手動でリンクする必要があります。
最初、targetに指定する引数が、xcodeのプロジェクトターゲットと同じものだと思っていたのですが、全く関係ないらしいです。
よくある(:production, :develpment, :test)とおなじです。
CocoaPodsでは、下記の3つのシンボルが用意されています。(任意の文字列を使ってもいい)
- :default(targetブロックを使ってないところ)
- :debug
- :test
platform :ios dependency 'JSONKit', '~> 1.4' dependency 'AFNetworking', '~> 0.7' dependency 'BlocksKit', '~> 1.0' dependency 'NSLogger' # Testing framework for Test target. target :test, :exclusive => true do dependency 'OCMock' dependency 'LRMocky' end
この構成だと下記の2つのstatic-libraryのターゲットを持つプロジェクトが生成されます。
- libPods.a (デフォルト)
- libPods-test.a (target :testで作ったやつ、ファイル名に指定した引数がsuffixとして付与される)
[Xcode][UI][iOS] XcodeでStoryboardのUser Defined Runtime Attributes使い方わかった
StoryBoardには、実行時、プロパティに値を設定できる機能があります。
例えばアニメーションやサイズの動的な変更でこれを使用すれば
最終調整で、プログラムでわざわざ値を修正して微調整するとかしなくてもよくなります。
手順
UITableView Cellでパフォーマンス向上のためにCellを再利用する機能がありますが、
そこで使用するIDをUser Defined Runtime Attributeで宣言してみます。
- ViewControllerにプロパティを宣言
- Storyboardの画面を開いてプロパティを宣言したViewControllerを選択
- Utility areaの「Show the identity inspector」タブを選択
- [+]をクリックして、必要なAttributeを追加
使用場面
Storyboard経由でViewControllerを呼び出すとき、`initWithNibName:bundle:`とか呼ばれないので、Viewの細かいサイズ変更などコードで行いたいが、プロパティのデフォルト値として定数値を設定したい場合。
`viewDidLoad`で毎回プロパティに値をセットするのはちょっと違うなというとき
例えば、独自のカスタムフォントを適用したが、Storyboard上では適用できないが、フォント名をプロパティのデフォルト値としてあらかじめ設定しときたいとか需要があると思います。
Sample Code
// MasterViewController.h #import <UIKit/UIKit.h> @interface MasterViewController : UITableViewController @property (strong, nonatomic) NSString *cellIdentifier; @property (strong, nonatomic) NSArray *cellTitleList; @end // MasterViewController.m #import "MasterViewController.h" @implementation MasterViewController @synthesize cellIdentifier = _cellIdentifier; @synthesize cellTitleList = _cellTitleList; [...] #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.cellTitleList = [NSArray arrayWithObjects:@"title1", @"title2", @"title3", nil]; } [...] #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.cellTitleList count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.cellIdentifier]; } cell.textLabel.text = [self.cellTitleList objectAtIndex:indexPath.row]; return cell; }
Closure Compilerを使用したときのオブジェクトメンバに関する注意点
Closure Compilerでスクリプトを最適化する場合、変数名等は短縮されてしまいます。
そのため、例えばオブジェクトのメンバー参照時に問題がおこることがあります。
例えば、Object(連想配列)の初期化時と参照方式に、文字列添字のみ等を使った場合は問題がおこらないのに、
初期化時のメンバー名と参照方式を混在した場合は、正しくメンバーを参照できない問題が発生します。
確認
ということで、Closure compilerの出力結果を確認してみました。
元のスクリプト
var obj = { name1: 'foo', // メンバー名(key)が最適化時に短縮される 'name2': 'bar' // こっちはそのまま }; var result = 'obj.name1: ' + obj.name1; result += '<br />'; result = 'obj["name2"]: ' + obj['name2']; result += '<br />'; //ADVANCED_OPTIMIZATIONの時に正しくメンバー参照できない result = 'obj.name2: ' + obj.name2; result += '<br />'; result += 'obj["name1"]: ' + obj['name1'];
SIMPLE_OPTIMIZATINOS
var obj = {name1:"foo", name2:"bar"}, result = "obj.name1: " + obj.name1; result += "<br />"; result = 'obj["name2"]: ' + obj.name2; result += "<br />"; result = "obj.name2: " + obj.name2; result += "<br />"; result += 'obj["name1"]: ' + obj.name1;
ADVANCED_OPTIMIZATIONS
var a = {a:"foo", name2:"bar"}, b = "obj.name1: " + a.a; b += "<br />"; b = 'obj["name2"]: ' + a.name2; b += "<br />"; b = "obj.name2: " + a.b; b += "<br />"; b += 'obj["name1"]: ' + a.name1;
なんだと...
追試
ではこれは?
メンバー名が非文字列のみ
元のコード
// メンバー名(key)は短縮される var obj = { name1: 'foo', name2: 'bar' }; var result = 'obj.name1: ' + obj.name1; result += '<br />'; result += 'obj.name2: ' + obj.name2; result += '<br />'; // メンバー名が非文字列なのでADVANCED_OPTIMIZATIONSで短縮されてしまうので最適化後エラー result += 'obj["name1"]: ' + obj['name1']; result += '<br />'; result = 'obj["name2"]: ' + obj['name2'];
ADVANCED_OPTIMIZATIONS
var a = {a:"foo", b:"bar"}, b = "obj.name1: " + a.a; b += "<br />"; b += "obj.name2: " + a.b; b += "<br />"; b += 'obj["name1"]: ' + a.name1; b += "<br />"; b = 'obj["name2"]: ' + a.name2;
メンバー名が文字列のみ
元のコード
// メンバー名(key)は短縮されない var obj = { 'name1': 'foo', 'name2': 'bar' }; var result = 'obj["name1"]: ' + obj['name1']; result += '<br />'; result = 'obj["name2"]: ' + obj['name2']; result += '<br />'; // メンバー名が文字列なので、ADVANCED_OPTIMIZATIONSでメンバー名(key)は短縮されないので最適化後参照エラー result += 'obj.name1: ' + obj.name1; result += '<br />'; result += 'obj.name2: ' + obj.name2;
SIMPLE_OPTIMIZATIONS
var obj = {name1:"foo", name2:"bar"}, result = 'obj["name1"]: ' + obj.name1; result += "<br />"; result = 'obj["name2"]: ' + obj.name2; result += "<br />"; result += "obj.name1: " + obj.name1; result += "<br />"; result += "obj.name2: " + obj.name2;
ADVANCED_OPTIMIZATIONS
var a = {name1:"foo", name2:"bar"}, b = 'obj["name1"]: ' + a.name1; b += "<br />"; b = 'obj["name2"]: ' + a.name2; b += "<br />"; // メンバー名が文字列なので、ADVANCED_OPTIMIZATIONSでメンバー名(key)は短縮されないので最適化後参照エラー b += "obj.name1: " + a.a; b += "<br />"; b += "obj.name2: " + a.b;
結論
- 文字列添字は短縮されない
- 非文字列添字は短縮される
Closure-library単体で、WebAPI Libraryとして作成した場合は、
オブジェクトのメンバーにドット参照でアクセスしていなければADVANCED_OPTIMIZATIONSをかけて、
jQueryとかその他Libraryが混在してそうなアプリ側のスクリプトは、SIMPLE_OPTIMIZATIONSにすればいいのか?
混ぜるな危険ってことですね...
Slim3のGlobalTransactionをよくわからないけど使ってみた
親子関係のある複数のモデルをLocal Transactionを使って保存する場合、Keyによって関連を定義しないと処理を行えないらしいです。
トランザクション - Google App Engine — Google Developers
Slim3にはGlobalTransactionがあるのでEntityGroupを気にしなくても整合性を保って更新できるって事なので使ってみました
環境
- Slim3 version 1.0.12
- App Engine SDK 1.5.2
汎用的に処理を行うため、Handlerクラスを定義してます。
- PersistWithTx: Global Transactionで永続化
- DataHandler: 永続化のための処理
- Person: Personモデル(親)
- Address: Addressモデル(子)
- PersonService: サービスクラス
ソース
Global Transactionを使って永続化を行う処理
モデル処理の実行サンプル
新規登録のテストケース
こんな感じ?
package jp.rh7.service; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; /* [ ... ]: その他パッケージ */ public class PersonServiceTest extends AppEngineTestCase { /* [ ... ] */ @Test public void createTerm() throws Exception { // Matchers Person chkPersonModel = new Person(); chkPersonModel.setName("山田 太郎"); Address addrModel = new Address(); addrModel.setZipCode("123-4567"); addrModel.setAddress("東京都なんとか区なんとかかんとか1-2"); /* Excecute */ service.createTerm(chkPersonModel, addrModel); List<Person> storedPersonList = QueryManager.getPersonList(); /* result */ assertThat(storedPersonList, is(notNullValue())); assertThat(storedPersonList.size(), is(1)); Person storedPersonModel = storedPersonList.get(0); assertThat(storedPersonModel, is(notNullValue())); List<Address> storedAddrList = storedPersonModel.getAddressListRef().getModelList(); assertThat(storedAddrList, is(notNullValue())); assertThat(storedAddrList.size(), is(1)); Address storedAddrModel = storedAddrList.get(0); assertThat(storedAddrModel, is(notNullValue())); /* Check save values */ /* [ ... ] */ } }