勉強日記

チラ裏

Programming TypeScript ch10 Namespaces.Modules

www.oreilly.com


  • モジュールについて議論するにあたり、次のことを区別することが肝要
    • (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()
}

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
"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
    • 詐欺