「Angular v2は事前コンパイルすることができる」ということを聞いても何のことだろうと思ってしまう。何故このように事前コンパイルする必要があるかというと「開発したAngular2アプリケーションをより効率の良いもの」にするためです。この「効率」というものを具体的にコードを書いて見ます。サンプルコードはAngular2チュートリアルを使っています。
事前コンパイルはngcコマンドで行いますが、そのモジュールが@angular/compiler-cliから提供されます。これにtsconfig.aot.json
というファイルを追加することと、package.json
に"ngc": "ngc -p ./tsconfig.aot.json",
というスクリプトを定義すること、そしてbootstrapを定義しているmain.ts
に対してAoT(Ahead-of-Time)ファイルmain.aot.ts
を定義することです。
// tsconfig.aot.json { "compilerOptions": { "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": false, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "pretty": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "noImplicitAny": true, "noImplicitReturns": true, "noImplicitUseStrict": false, "noFallthroughCasesInSwitch": true, "outDir": "./.tmp", "rootDir": "./src", "types": [ "node" ] }, "angularCompilerOptions": { "debug": true }, "compileOnSave": false, "files": [ "src/main.ts" ], "exclude": [ "node_modules", "bin" ] }
package.json
にはAoT意外にファイルを圧縮するコマンドも追加しています。
// package.json "scripts": { "ngc": "ngc -p ./tsconfig.aot.json", "minify": "uglifyjs bin/bootstrap.bundle.js --screw-ie8 --compress --mangle --output bin/bootstrap.bundle.min.js", "minify:aot": "uglifyjs bin/bootstrap.aot.bundle.js --screw-ie8 --compress --mangle --output bin/bootstrap.aot.bundle.min.js", "build": "npm run ngc && npm run webpack && npm run minify && npm run minify:aot", "start": "concurrently \"node server/app.js\" \"npm run lite\"", "webpack": "webpack --config webpack.config.js", "tsc": "tsc", "tsc:w": "tsc -w", "lite": "lite-server -c bs-config.js", "test": "karma start karma.conf.js" },
main.ts
とmain.aot.ts
を比較します。
// main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './shared/app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
// main.aot.ts import { platformBrowser } from '@angular/platform-browser'; import { enableProdMode } from '@angular/core'; import { AppModuleNgFactory } from './shared/app.module.ngfactory'; enableProdMode(); platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
幾つか違いがあります。platformBrowser
とplatformBrowserDynamic
やAppModule
とAppModuleNgFactory
など。特にNgFactory.tsというファイルがngcコマンド実行後に生成されますが、これが最適化されたファイルです。
そして今回はSystemJSではなくWebpackを利用しています。その設定ファイル
// webpack.config.js 'use strict'; let path = require('path'); module.exports = { entry: { 'bootstrap': './src/main.ts', 'bootstrap.aot': './src/main.aot.ts' }, output: { path: './bin', filename: '[name].bundle.js' }, module: { loaders: [ { test: /\.ts$/, loader: 'ts', query: { configFileName: 'tsconfig.json' } } ] }, resolve: { root: [ path.join(__dirname, 'src') ], extensions: ['', '.ts', '.js'] }, devtool: false };
実際にngcコマンドを実行しwebpackを使ってjsを出力したファイルのサイズを比較します。
$ ls -lh total 11600 1.7M 9 16 18:19 bootstrap.aot.bundle.js 536K 9 16 18:19 bootstrap.aot.bundle.min.js 2.6M 9 16 18:19 bootstrap.bundle.js 813K 9 16 18:19 bootstrap.bundle.min.js
AoTでは特にテンプレートをJavaScriptに事前変換します(それ意外に多くのことを行っています)。例えば、テンプレートを次のように定義します。
@Component({ selector: 'hero-search', template: ` <div id="search-component"> <h4>Hero Search</h4> <input #searchBox id="search-box" (keyup)="search(searchBox.value)" /> <div> <div *ngFor="let hero of heroes | async" (click)="gotoDetail(hero)" class="search-result" > <p class="ashiras">{{hero.name}}</p> </div> </div> </div> `,
通常は
core_1.Component({ selector: 'hero-search', template: "\n <div id=\"search-component\">\n <h4>Hero Search</h4>\n <input #searchBox id=\"search-box\" (keyup)=\"search(searchBox.value)\" />\n <div>\n <div *ngFor=\"let hero of heroes | async\"\n (click)=\"gotoDetail(hero)\" class=\"search-result\" >\n <p class=\"ashiras\">{{hero.name}}</p>\n </div>\n </div>\n </div>\n ", styleUrls: ['hero-search.component.css'], providers: [hero_search_service_1.HeroSearchService] }),
AoTでは完全なJavaScriptに変換されています
function (rootSelector) { this._el_0 = this.renderer.createElement(null, 'div', this.debug(0, 5, 6)); this.renderer.setElementAttribute(this._el_0, 'class', 'search-result'); this._text_1 = this.renderer.createText(this._el_0, '\n ', this.debug(1, 6, 60)); this._el_2 = this.renderer.createElement(this._el_0, 'p', this.debug(2, 7, 8)); this.renderer.setElementAttribute(this._el_2, 'class', 'ashiras'); this._text_3 = this.renderer.createText(this._el_2, '', this.debug(3, 7, 27)); this._text_4 = this.renderer.createText(this._el_0, '\n ', this.debug(4, 7, 44)); var disposable_0 = this.renderer.listen(this._el_0, 'click', this.eventHandler(this._handle_click_0_0.bind(this))); this._expr_1 = import9.UNINITIALIZED; this.init([].concat([this._el_0]), [ this._el_0, this._text_1, this._el_2, this._text_3, this._text_4 ], [disposable_0], []); return null; };
より詳しい説明は @mgechev が書かれてますので一読下さい。
公式サイトにもあります。