generated at
TypeScript Transformerを使って任意の構文木を書き換えるシンプルなWebアプリの例

やりたいこと
TypeScriptで書かれたWebアプリの構文木書き換えてビルドしたい。
なるべくミニマルなTypeScript Transformerの例を作成したい。

実際に動作するリポジトリ
このページの内容は以下のGitHubのリポジトリにある。

npm run server した後にhttp://localhost:8080/にアクセスすれば動作が確認できる。

やりかた
今回の技術にあたっての貴重な日本語の資料「TypeScript Transformerについてのお話 - Qiita」。
上記の資料でも触れられているが tsconfig.json でCustom Transformersを指定すること現状ができない。
ttypescriptを使うことで tsconfig.json で指定できるようになるようだが変わったことをすると後でハマったり融通が利かなくなる経験則があるので避けた。

そこでWebpackts-loaderを使う。ts-loaderはよく使うのでこれでできれば複雑になったときに変なことでハマることが少ないと思う。

まず、シンプルなTypeScript Transformerを作る。
上記の日本語記事を参考に alert console.log に変換するTypeScript Transformerを作った。
transformers/my-transformer.ts
// (base: https://qiita.com/Quramy/items/1c9c2f7da2a6548f6901) import * as ts from 'typescript'; // Transformer for alert("...") => console.log("[alert]: ..."); export default function (ctx: ts.TransformationContext) { function visitNode(node: ts.Node): ts.Node { if (!isAlert(node)) { return ts.visitEachChild(node, visitNode, ctx); } return createHelper(node); } function createHelper(node: ts.Node) { ctx.requestEmitHelper({ name: "ts:say", priority: 0, scoped: false, text: "var __alert_console__ = function(msg) { console.log('[alert]: ' + msg); };", }); return ts.setTextRange(ts.createIdentifier("__alert_console__" ), node); } function isAlert(node: ts.Node): node is ts.PropertyAccessExpression { return node.kind === ts.SyntaxKind.Identifier && node.getText() === "alert"; } return (source: ts.SourceFile) => ts.updateSourceFileNode(source, ts.visitNodes(source.statements, visitNode)); }

以下のように tsc transformers/*.ts transformers/*.js として配置されるようにあらかじめビルドされるようにしている。
package.json
... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build-transformers": "tsc transformers/*.ts", "serve": "npm run build-transformers && webpack-dev-server --watch", "build": "npm run build-transformers && webpack" ....

あとは webpack.config.js で上記のTypeScript Transformerを使うように設定した。
webpack.config.js
// 自分のTransformerをインポート const mytransformer = require('./transformers/my-transformer').default; ... module.exports = { ... module: { rules: [ ... { test: /\.ts$/, exclude: /node_modules/, loader: 'ts-loader', // 以下を追記 options: { getCustomTransformers: () => ({ before: [mytransformer] }), transpileOnly: true, }, } ] }, ...

main.ts alert() を使った何かを書く。
main.ts
... alert("hello, world");

実際の変更のコミット

実際の動作
alert() しているが alert されずにコンソールに出力されていることが確認できる。

TypeScript Transformerを使えば、TypeScriptの型情報を元にしたASTの変換も書けるし、ブラウザで非対応の技術を対応している技術に変換して動かすようなこともできるし、色々できそう。

その他の参考にした資料

おまけ

以下(おそらくソースマップ)を見ると alert() のままになっている。変換後のTypeScriptだとよかったのだが。これだとデバッグ時はすごく困難な気がする。