最近人気のAngular2の–prodでビルド(プロダクションビルド)する際のエラーについてです。
ng buildやng serveでは問題なくても–prodを付けたところ謎エラーが発生する事があります。
エラーの対策が知りたい方は準備を飛ばしてください。
準備
とりあえずプロダクションビルドでエラーを発生させるためのサンプルを用意します。
- node.js
- @angular/cli (-g)
- VS Code
はそれぞれ最新が入ってる前提で行きます。
プロジェクト用意
VS Codeで適当なフォルダを作成し開きます。
Ctrl + @でターミナルを出したらcliプロジェクトを生み出します。
ng new Sample cd Sample
簡単ですね。
適当なコンポーネントを作ります。
ng g c sample-home
app.component.html
<h1> {{title}} </h1> <app-sample-home></app-sample-home>
これで準備完了です。
とりあえずビルド
とりあえず先ほど作ったコンポーネントが呼び出されるようにしてビルドします。
ng build --prod
これはまだprodでも大丈夫です。
private変数のエラー
通常ビルドでは問題なかったのに突如privateがどうのと言われだします。
原因は単純でhtmlテンプレートからcomponentのプライベート変数を束縛している場合に発生します。
エラーが起こる記述
sample-home.component.html
<div> {{contents}} </div>
sample-home.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-sample-home', templateUrl: './sample-home.component.html', styleUrls: ['./sample-home.component.css'] }) export class SampleHomeComponent implements OnInit { private contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } }
privateな文字列contentsをhtmlテンプレートから{{contents}}で束縛しています。
$ ng build --prod ERROR in E:/projects/angular/prodtest/Sample/src/$$_gendir/app/sample-home/sample-home.component.ngfactory.ts (30,30): Property 'contents' is private and only accessible within class 'SampleHomeComponent'.
ビルドするとこうなります。
対策
簡単です。
html側で利用する変数・メソッドはprivateを外します。
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-sample-home', templateUrl: './sample-home.component.html', styleUrls: ['./sample-home.component.css'] }) export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } }
とりあえずオブジェクト指向的にはアレですが、基本的にコンポーネントが他から参照されることは無いと思いますので、原則コンポーネントにはprivateを使わない方が良いでしょう。
ただし、constructorの仮引数のところのprivateをどけるとインスタンス変数にならないので注意です。
何やらシンボルが解釈できないエラー
エラーが起こる記述
import { Component, OnInit, HostListener } from '@angular/core'; const kHtml: string = 'html'; @Component({ selector: 'app-sample-home', //templateUrl: './sample-home.component.html', templateUrl: `./sample-home.component.${kHtml}`, styleUrls: ['./sample-home.component.css'] }) export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } }
@Componentの中で変数をテンプレートリテラルを使って参照しています。
ビルドすると以下のようになります。
$ ng build --prod ERROR in Error encountered resolving symbol values statically. Expression form not supported (position 8:16 in the original .ts file), resolving symbol SampleH omeComponent in E:/projects/angular/prodtest/Sample/src/app/sample-home/sample-home.component.ts
どうも対応していないらしいです。
対策
デコレータの中ではテンプレートリテラルを利用せず+で文字列をくっつけます。
import { Component, OnInit, HostListener } from '@angular/core'; const kHtml: string = 'html'; @Component({ selector: 'app-sample-home', //templateUrl: './sample-home.component.html', templateUrl: './sample-home.component.' + kHtml, styleUrls: ['./sample-home.component.css'] }) export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } }
この例は大分意味がない物ではありますが、私の場合はアニメーションの数値を定数にして使ってたらこれになりました。
何やらシンボルが解釈できないエラーその2
エラーが起こる記述
import { Component, OnInit, HostListener } from '@angular/core'; function html(): string { return './sample-home.component.html'; } @Component({ selector: 'app-sample-home', templateUrl: html(), styleUrls: ['./sample-home.component.css'] }) export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } }
先ほどと似たよう感じで次はテンプレートhtmlのファイル名の指定を関数に変えましたがこれもエラーです。
$ ng build --prod ERROR in Error encountered resolving symbol values statically. Reference to a non-exported function (position 3:10 in the original .ts file), resolving symbol SampleHomeComponent in E:/projects/angular/prodtest/Sample/src/app/sample-home/sample-home.component.ts
対策
どうしようもないので関数は使わないで文字列定数を使ってください。
何やらマッチしないエラー
TODO: 起こる条件がイマイチ不明瞭
Supplied parameters do not match any signature of call target.
エラーが起こる?記述
import { Component, OnInit, HostListener } from '@angular/core'; @Component({ selector: 'app-sample-home', templateUrl: './sample-home.component.html', styleUrls: ['./sample-home.component.css'] }) /** テストコードですよ */ export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } /** ウインドウ幅が変更された場合 */ @HostListener('window:resize', ['$event']) onWindowSizechanged() { } }
イベントの引数が指定されているのに、関数には引数が無い場合に起こる……ような気がします。
何が気がしますかというと、新規で作ったら起きないけど、既存のそこそこ規模のやつだと起きるので……
どうも@angular/compiler-cliのバージョンの問題の様です。最新ではこのエラーは発生しません。
対策
import { Component, OnInit, HostListener } from '@angular/core'; @Component({ selector: 'app-sample-home', templateUrl: './sample-home.component.html', styleUrls: ['./sample-home.component.css'] }) /** テストコードですよ */ export class SampleHomeComponent implements OnInit { /* private */ contents: string = 'サンプルコンテント'; constructor() { } ngOnInit() { } /** ウインドウ幅が変更された場合 */ @HostListener('window:resize', ['$event']) onWindowSizechanged(event: any) { } }
イベントの引数をちゃんと付けてください。
原因
開発中は問題なかったのにprodビルドをしたとたん発生するコレ。理由は多分なんとなくわかると思われますが、最適化の制限の様です。
最近のAngular2ではプロダクションビルドではパフォーマンス向上のためにAoTという仕組みが利用されています。(以前はオプションだったようですが、今ではデフォルトがこれの様です。)
これはビルドの段階でより効率の良いものへコンパイルしなおす(特にHTMLテンプレートとCSS)というもので、実行時に最適化を行うJITと対比したような仕組みです。
通常の開発ビルドではHTMLの内容は実行時に解析されるようですが、AoTではビルド時にtsのコードの変換しているようです。
この際に問題になるのが2点あります。1つはJavaScriptとしては問題ないがTypeScriptとしては問題のあるコードです。
実行時に処理が行われる開発ビルドでは勿論JavaScriptとして解析されます。この場合多少型が違ったりしても問題ありません。
AoTの場合は事前にTypeScriptにされる為、型の違いなど許容できないことが増えてきます。
またもう1つはHTMLテンプレートを処理する為にコンポーネントとは別のクラスを作って両者を繋げている点です。実際には自身が作ったのとは別のソースがビルドされているため、予期しないエラーが発生します。privateの制約もこのあたりで出てきているようです。