TL;DR
BabelでTypescriptのnamespace、module構文を扱う場合、.babelrc
でallowNamespaces
を有効にする
{
"plugins": [
[
"@babel/plugin-transform-typescript",
{
"allowNamespaces": true
}
],
]
}
概要について
Next.jsでアプリを開発していて、TypeScriptのnamespace
構文を使ってみた所、詰まったので解決策を残しておきます。
事象について
下記ディレクトリ構造、ソース、コンパイル時のエラー文です。
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()
は文字列を返す
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
文字列を返す
export namespace X {
export const method = (): string => {
return "Hello"
}
}
next
コマンドでビルドサーバ起動時にコンパイルエラー
[ 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についてはこの記事で分かりやすく記述されています。
Next.jsのバージョン9が2019年8月8日にリリースされました。
Next.js 9ではTypeScriptをビルトインサポートしています、TypeScriptを利用した場合でも、
webpack.config.js
、next.config.js
等の設定は要りません。そしてNext.jsではバンドラにWebpackを利用していて、loaderにはデフォルトではBabelを利用しています。
なので、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が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
コマンドを実行してください。
{
"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
を作成し、下記をコピペしてください。
{
"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
コマンドを実行するとエラー発生しません。
// 抽象的な名前空間を宣言
declare namespace X {
export type method = () => string
}
// インターフェースに従って実装
const method: X.method = () => 'Hello'
console.log(method())
しかしindex.ts
に下記をコピペし、npm run build
コマンドを実行するとエラーが発生します。
// 実装された名前空間を宣言
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を確認すると確かに書いてありました。
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
コマンドが成功するはずです。
{
"presets": [
"@babel/env",
"@babel/typescript"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread",
[
"@babel/plugin-transform-typescript",
{
"allowNamespaces": true
}
],
]
}
namespace X {
export const method = () => `Hello`
}
console.log(X.method())