-
モジュールについて議論するにあたり、次のことを区別することが肝要
- (a) tscによるモジュール解決
- (b) ビルドシステムによるモジュール解決
- webpack
- gulp
- etc.
- (c) 実際にどのようにしてアプリケーションにロードされ実行されるか
<script>
- SystemJS
- etc.
- webpackなんかはこの3つを良しなにやってくれる
- 本章では(a)について学ぶ
A Brief History of JavaScript Modules
-
歴史
- JS誕生 (1995)
- モジュールシステムのたぐい無し
-
IIFE: Immediately Invoked Function Expression で名前汚染を防ぎつつ、モジュール横断的に必要な機能は
window
に代入されいた- explicitなAPIではない
- 一度にすべて読み込むと遅いので、動的にスクリプトを読み込むようになってきた
-
モジュールローダ
- Dojo (2004)
- YUI (2005)
- LABjs (2009)
-
モジュールが満たすべき条件
- カプセル化されていること
- 依存がexplicitであること
- さもないと、どの順番で読み込めばよいかわからない
- 各モジュールはアプリケーション中で一意の識別子を有すること
- NodeJS (2009)
- モジュールシステム搭載
-
CommonJS
require
/module.exports
- AMDモジュール標準 (2008)
- DojoとRequireJSによる後押し
define()
- Browserify (2011)
- フロントエンドでCommonJS相当のことができるようになった
import
/export
シンタックス
-
CommonJSの問題
- requireは同期呼び出しであり、ブラウザでの実行に適さない
- requireの引数は任意の式を受け取ることができ、必ずしも静的に解析できない
- ので、プログラムを実行するまで、参照先ファイルが本当に存在するかわからない
- この問題を解消するために
import
/export
シンタックスが生まれた -
各処理系向けにコンパイルする必要がある
- NodeJS: CommonJS
- ブラウザ: globals等、何らかの読み込める形
import, export
- 基本は
import
/export
を使うべき
a.ts
export function foo() {}
export function bar() {}
b.ts
import {foo, bar} from './a'
foo()
export let result = bar()
- default export
c.ts
export default function meow(loudness: number) {}
d.ts
import meow from './c'
meow(11)
- ぜんぶ
e.ts
import * as a from './a'
a.foo()
a.bar()
- re-export
f.ts
export * from './a'
export {result} from './b'
// default exportのre-export。書籍サンプルには書いてあるが動かない
export meow from './c'
- Companion Object Pattern
g.ts
export let X = 3 // value
export type X = {y: string} // type
import {X} from './g' // import value and type
let a = X + 1
let b: X = {y: 'z'}
Dynamic Imports
- 初期レンダリング性能を上げる
(async () => {
const a = await import('./a')
a.foo()
a.bar()
})()
Using CommonJS and AMD Code
import
/export
そのまま使える
Module Mode Versus Script Mode
-
ヒューリスティック
import
/export
があるのはmodule mode- ないのはscript mode
- 大抵module mode
- module modeは、ファイル間でコードを参照するのに
import
/export
必要 -
script modeは、トップレベルの変数はすべて他のファイルから見える
import
不要
-
script modeのユースケース
- プロトタイプをさくっと作りたい
- 型定義ファイル
Namespaces
- カプセル化のもうひとつの方法
- ただし、moduleのほうを優先すべき
Util.ts
namespace Util {
export function foo() {}
export namespace Nested {
export function bar() {}
}
}
namespace App {
Util.foo()
Util.Nested.bar()
}
- import不要
- ひいては、ファイルシステム上のファイル名を意識する必要なし
Collisions
- 重複不可
Util.ts
namespace Util {
export function foo() {} // Error: Duplicate function implementation.
}
Util2.ts
namespace Util {
export function foo() {} // Error: Duplicate function implementation.
}
App.ts
namespace App {
Util.foo()
}
Compiles Output
"use strict";
var Util;
(function (Util) {
function foo() { }
Util.foo = foo;
})(Util || (Util = {}));
//# sourceMappingURL=Util.js.map
- tsconfig.jsonのmoduleの設定いかん問わず、IIFE + globalsにコンパイルされる
- cf. import/exportを使った
a.ts
のコンパイル結果
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function foo() { }
exports.foo = foo;
function bar() { }
exports.bar = bar;
//# sourceMappingURL=a.js.map
Column: Prefer Modules over Namespaces When Possible
- 依存をexplicitに表現できるmoduleのほうが良い、という話
Declaration Merging
- namespaceはvalue以外のあらゆるものとmerge可能
Exercises
enumにstatic method追加
enum MyEnum {
FOO = 'foo',
BAR = 'bar'
}
namespace MyEnum {
export function hoge(): void {}
}
const foo = MyEnum.FOO
MyEnum.hoge()
- enumとnamespaceをmerge
英語
-
nitty-gritty
- 本質
-
scam
- 詐欺