こんにちは。きんくまです。
TypeScriptで内部モジュールを使うときのことです。(外部モジュールじゃないです)
>> 内部モジュール – TypeScript クイックガイド – phyzkit.net
仕事でクラスファイルが多くなり、1ファイル1クラスにして、各ファイルにそれぞれreferenceを書いていました。
そしたらコンパイルは通って js は生成されるのに、実行時にこんなエラーが発生して困りました!!
Uncaught TypeError: Cannot read property 'prototype' of undefined main.js:4 Uncaught TypeError: undefined is not a function
コンパイルが通るのに、エラーが出てしまい原因が特定できず大変困りました。
すると、reference部分の順番を入れ替えると戻ることがありました。
クラスを継承していたり、moduleが分かれているといろいろと問題ありそうです。
それをTwitterでつぶやいたところ、教えてもらいました!ありがとうございます!!
クラスごとのファイルに分けて開発したいから何とかしたいんだけど、この辺のノウハウはないだろうか?? 例えば、ファイルごとにreference文を書くのではなく、プロジェクトで1箇所にまとめてしまうとか。
— きんくまデザイン (@kinkuma_design) 2014, 10月 31
@kinkuma_design が、がんばります!
とりあえず、僕の基本方針としては最初のドキュメントクラスっぽいところになるべくrefferenceをまとめ書きしておいて、完全に依存/専属関係にあるものだけは、都度、参照元に書くというような書き方をしてますね。
— Satoshi Onoda (@satoshionoda) 2014, 10月 31
@kinkuma_design module 単位で .js を出力。同一 module 内の reference は .ts を読む。個々の .js .d.ts は出力せず module 間の解決のみ .d.ts で行う。これで今のとこ問題なく1ファイル1クラスに出来ています。
— Hirofumi Kawakita (@k1623_) 2014, 10月 31
@kinkuma_design ts は出力した js 毎に都度実行しクラスを解決するため、ファイルの連結順序をへまる(例えば継承元のファイルが後になる等)とエラーになります。その解決のためにはクラスを増やすたびに concat の順序指定を書く羽目になってしまい超面倒です。
— Hirofumi Kawakita (@k1623_) 2014, 10月 31
@kinkuma_design 一方複数の ts をまとめて tsc で js にすると、そのあたりの面倒をコンパイラが見てくれるので安全にひとつのファイルにすることが出来ます。自分も2ヶ月くらいトライアンドエラーでここにたどり着きましたが、絶対に大丈夫かは自信がないですが。。。
— Hirofumi Kawakita (@k1623_) 2014, 10月 31
それでどうやるか自分でも実験してみました。
どうやって内部モジュールをまとめるか
アイデアをいただいていろいろと試したところ、以下のようにしようかなと思いました。
1. moduleごとにjsファイルは分けてコンパイル
(最終的にはそれを、使用される順に連結して1枚にしてもOK)
ここでコンパイラに指定するtsファイルを、エントリクラスファイルとする。
2. moduleごとにそのmodule内のエントリクラスファイルを除いた全てのファイルを参照する専用ファイルを1枚作る
3. moduleのエントリクラスファイルから2のファイルだけを参照する
4. その他のファイルには一切 reference を書かない
2の参照ファイルを書く際には約束事があって
・継承を行うときは、必ずサブクラスは親クラスの後に書く
(これをしないと冒頭のCannot read property ‘prototype’エラーがでます)
・declarationファイル(.d.tsファイル)の位置はどこでもOK。
でも別moduleを参照する際は必ず記入
実際に試してみる
1. moduleごとにjsファイルは分けてコンパイル
今回はサンプルで2つのモジュールを使います。なので、2つコンパイルします。
gruntの設定はこんなのとか。
typescript:{ qmodule:{ src:[ 'htdocs/src/q_Q.ts' ], dest:'htdocs/q_q.js', options:{ basePath:'htdocs/src', target: 'es5', sourceMap: false, declaration: true } } ,default:{ src:[ 'htdocs/src/main.ts' ], dest:'htdocs/main.js', options:{ basePath:'htdocs/src', target: 'es5', sourceMap: false } } }
2. moduleごとにそのmodule内のエントリクラスファイルを除いた全てのファイルを参照する専用ファイルを1枚作る
今回のサンプルではAクラスを継承したM_extAクラスがあるとします。
kk_file_refs.ts
/* * declaration files */ /// <reference path="jquery.d.ts" /> /// <reference path="../q_q.d.ts" /> /* * class files * * A.ts と M_extA.ts の位置を入れ替えると実行時エラー */ /// <reference path="A.ts" /> /// <reference path="C.ts" /> /// <reference path="B.ts" /> /// <reference path="M_extA.ts" />
3. moduleのエントリクラスファイルから2のファイルだけを参照する
main.ts
/// <reference path="kk_file_refs.ts" /> module kk{ export class Main{ constructor(){ console.log('hello'); new M_extA(); } } }
4. その他のファイルには一切 reference を書かない
例えばこんな感じに全く書きません。
module kk{ export class M_extA extends A{ constructor(){ super(); console.log('M extends A'); new B(); new q.Q(); } } }
ファイル名とかぐっちゃぐちゃだけど、参考までにデータ一式アップします
internal_module.zip
とりあえずこれで整理はできそうかも。また何か問題があったら追記します。
エラーのおきていた70ファイルぐらいに分かれていたプロジェクトもこれで直りました。やったー。
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ