⚡️

Babel プラグインを TypeScript で開発する

2020/11/06に公開

Babel のプラグイン開発は、JavaScript や Flow/TypeScript のノードに対応した多くの API があり、API リファレンスを調べるだけでも面倒。どうせなら、VSCode の強力なサポートが得られる TypeScript で書きたい。

ということで、どんなふうに TypeScript で Babel プラグインを書いているかと、いくつか注意する点を紹介する。

パッケージ導入

特に複雑なことをしなければ、@babel/core だけで十分

$ npm i @babel/core

開発用に TypeScript コンパイラと Babel CLI。あとは必要な型定義を導入

$ npm i typescript @babel/cli @types/babel__core

@types/babel-core というパッケージも存在するので注意。 こちらの定義は古いので、@types/babel__core を入れましょう。

コード

最低限、types と NodePath, PluginObj の型があればいい。

import { types as t } from '@babel/core';
import type { NodePath, PluginObj } from '@babel/core';

Visitor はこんな感じ。NodePath に適切な型パラメータを与えれば、path.node も期待通りの型になる。

const visitor = {
  ClassDeclaration(path: NodePath<t.ClassDeclaration>) {
    const { node } = path;  // const node: t.ClassDeclaration
    // ...
  }
};

ノードの種別判定には、types.isXXX() を使おう。TypeScript の type predicates のおかげで、types.isXXX() を通過したあとのコードパスでは node が適切な型として認識される。

if (
  t.isCallExpression(node.expression) &&  // node.expression: t.Expression
  t.isSuper(node.expression.callee) &&    // node.expression: t.CallExpression
  node.expression.arguments.length === 1 &&
  t.isIdentifier(node.expression.arguments[0], { name: 'props' })
) {
  // ...
}

最後に、このモジュールから返す関数の型定義は多少トリッキーだ。

function transform(_api: typeof t): PluginObj {
  return {
    name: 'babel-example-plugin',
    visitor
  };
}

export default transform;

_api として渡されるのは @babel/coretypes なのだが、これは、

import * as t from '@babel/types';
...
export { ParserOptions, GeneratorOptions, t as types, template, traverse, NodePath, Visitor };

このように、namespace imports されたシンボルがエクスポートされており、そのままでは型としてい使用できない。そのため、ここでは typeof 演算子を使っている(もっとも、この例では _api を使っていないのだが...)

参考

Discussion