Babelでnamespace、moduleをトランスパイルする

### TL;DR

BabelでTypescriptのnamespace、module構文を扱う場合、`.babelrc`で`allowNamespaces`を有効にする

```json:.babelrc
{
    "plugins": [
        [
            "@babel/plugin-transform-typescript",
            {
                "allowNamespaces": true
            }
        ],
    ]
}
```

### 概要について

Next.jsでアプリを開発していて、TypeScriptの`namespace`構文を使ってみた所、詰まったので解決策を残しておきます。

### 事象について

下記ディレクトリ構造、ソース、コンパイル時のエラー文です。

```directory
C:.
|   .gitignore
|   next-env.d.ts
|   package-lock.json
|   package.json
|   README.md
|   tsconfig.json
|
+---components
|       Layout.tsx
|       List.tsx
|       ListDetail.tsx
|       ListItem.tsx
|
+---interfaces
|       index.ts
|
+---pages
|       about.tsx
|       detail.tsx
|       index.tsx
|       initial-props.tsx
|
\---utils
        index.ts
        sample-api.ts
```

`X.method()`を呼び出す、`X.method()`は文字列を返す

```pages/index.tsx
import * as React from 'react'
import Link from 'next/link'
import Layout from '../components/Layout'
import { NextPage } from 'next'
import { X } from '../utils/index'

const IndexPage: NextPage = () => {
  return (
    <Layout title="Home | Next.js + TypeScript Example">
      <h1>Hello Next.js 👋</h1>
      <p>
        {X.method()}
      </p>
      <p>
        <Link href="/about">
          <a>About</a>
        </Link>
      </p>
    </Layout>
  )
}

export default IndexPage
```

`X.method()`は`hello`文字列を返す

```util/index.ts
export namespace X {
    export const method = (): string => {
        return "Hello"
    }
}
```

`next`コマンドでビルドサーバ起動時にコンパイルエラー

```terminal
[ error ] ./utils/index.ts
SyntaxError: ~\nextapp\utils\index.ts: Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel.
To enable and review caveats see: https://Babeljs.io/docs/en/Babel-plugin-transform-typescript
> 1 | export namespace X {
    |                  ^
  2 |     export const method = (): string => {
  3 |         return "Hello"
  4 |     }
```

### Next.js, Babel, TypeScriptについて

上記のエラーを読みとく前に、Next.js、Babel、TypeScriptの関係について簡単にまとめてみたいと思います。

- Next.js

    Next.jsはIsomophic JavaScriptと呼ばれるSPAとSSRを実現できるフレームワークです。
    クライアントとサーバをJavaScriptで記述でき、ユーザビリティも高いです。

    Isomophic JavaScriptについては[この記事](https://qiita.com/kyrieleison/items/4ac5bcc331aee6394440)で分かりやすく記述されています。

    [Next.jsのバージョン9が2019年8月8日にリリースされました](https://nextjs.org/blog/next-9)。

    Next.js 9ではTypeScriptをビルトインサポートしています、TypeScriptを利用した場合でも、`webpack.config.js`、`next.config.js`等の設定は要りません。

    そしてNext.jsではバンドラにWebpackを利用していて、loaderには[デフォルトではBabel](https://www.npmjs.com/package/next)を利用しています。

    なので、Next.jsはts,tsxファイルをBabelを利用してバンドルします。
    
- TypeScript

    TypeScriptはaltJSと呼ばれる代替JavaScript言語です。
    JavaScriptは動的型付け言語なので型を記述出来ませんが、TypeScriptを利用するとJavaScriptを静的型付けで記述出来ます。

    型を宣言、指定出来る他、クラス、継承、インターフェース、ジェネリクス、名前空間などC++、Javaなどのオブジェクト指向言語で実装されている機能を利用できます。

    今回詰まったのは名前空間ですね。

- Babel

    トランスパイラと呼ばれるJavaScript構文変換を行うコンパイラです。
    ES6以降の次世代のJavaScriptを記述し、ソースをBabelでES5に変換することで主要なブラウザで動作できるというものです。

    余談ですが、最近ではF#のパイプ構文が追加されたようです、Bashのパイプが好きだったので嬉しいです、直接関係はありませんが。

    Babelの[バージョン7.5.0](https://github.com/Babel/Babel/releases/tag/v7.5.0)が2019年7月4日にリリースされました。

    Babel 7.5.0ではTypeScriptのnamespace構文が試験的にサポートされています。

    そしてNext.js 9ではparser等にBabel 7.5.0を使用しています。


まとめると、Next.js 9はBabel 7.5.0を使用していてBabel 7.5.0はTypeScriptのnamespace構文を試験的にサポートしているという事です。

TypeScriptを使ったことがなかったので、てっきりTypeScriptの構文はBabelによって完全にサポートされているかと思いましたが現状試験的なサポートの様です。

### 事象の解決

先に挙げたエラー文では`Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel.`と書かれています。

これはBabelでは`declare`で宣言されていない`namespace`をサポートしないと解釈できます。

検証のためにBabel 7.5の環境を構築してみます、適当な空のディレクトリに移動して、`package.json`を作成し、下記をコピペし、`npm i`コマンドを実行してください。

```package.json
{
  "name": "babel-namespace-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "type-check": "tsc --noEmit",
    "build": "babel index.ts --out-dir dist --extensions \".ts,.tsx\" --source-maps inline",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/plugin-proposal-class-properties": "^7.5.5",
    "@babel/plugin-proposal-numeric-separator": "^7.2.0",
    "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-typescript": "^7.3.3",
    "typescript": "^3.5.3"
  }
}
```

また`.babelrc`を作成し、下記をコピペしてください。

```json:.babelrc
{
    "presets": [
        "@babel/env",
        "@babel/typescript"
    ],
    "plugins": [
        "@babel/proposal-class-properties",
        "@babel/proposal-object-rest-spread",
        [
            "@babel/plugin-transform-typescript",
            {
                "allowNamespaces": false
            }
        ],
    ]
}
```

`index.ts`を作成し、下記をコピペし、`npm run build`コマンドを実行するとエラー発生しません。

```index.ts
// 抽象的な名前空間を宣言
declare namespace X {
    export type method = () => string
}

// インターフェースに従って実装
const method: X.method = () => 'Hello'

console.log(method())
```

しかし`index.ts`に下記をコピペし、`npm run build`コマンドを実行するとエラーが発生します。

```index.ts
// 実装された名前空間を宣言
namespace X {
    export const method = () => `Hello`
}

console.log(X.method())
```

これはBabelが`declare`で宣言された名前空間のみをサポートすることを意味します。

例えば`npm run type-check`を実行した場合、上記の例はどちらとも通ります。
つまりTypeScriptの観点では構文自体は合っていると解釈できます。

エラー文には`To enable and review caveats see: https://Babeljs.io/docs/en/Babel-plugin-transform-typescript`、つまり実装的な名前空間を有効にする方法はURLを見てね.と書いてあるので[URL](https://Babeljs.io/docs/en/Babel-plugin-transform-typescript)を確認すると確かに書いてありました。

> namespacess not marked with declare are experimental and disabled by default. Not enabling will result in an error: "Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel."

> Workaround: Enable the allowNamespaces option.

`allowNamespaces`オプション付けることで回避出来る書いてあります。

余談ですが`tsconfig.json`にオプションを記述してエラーになりました。
namespaceをトランスパイルするのはBabelなので`.babelrc`に記述するのが正解ですね。

なので、`.babelrc`を下記の様な状態にすることで、下記の`index.ts`はトランスパイル出来ます。
`npm run build && node dist/index.js`コマンドが成功するはずです。

```json:.babelrc
{
    "presets": [
        "@babel/env",
        "@babel/typescript"
    ],
    "plugins": [
        "@babel/proposal-class-properties",
        "@babel/proposal-object-rest-spread",
        [
            "@babel/plugin-transform-typescript",
            {
                "allowNamespaces": true
            }
        ],
    ]
}
```

```index.ts
namespace X {
    export const method = () => `Hello`
}

console.log(X.method())
```