AEのスクリプトをTypeScriptで書く

環境

TypeScript

インストール

Node.jsが入ってる前提で始める。

typescriptはnpm経由で以下のようにすればグローバルにインストールされる。

npm i -g typescript

ナイトリービルド版は、@nextをつけて。

npm i -g typescript@next

今ちょうどTypeScriptの2.0.0のベータ版が出てて、割りと痒いところに手が届くようになったので、それを使いたい場合は、

npm i -g typescript@beta

とすればよい。

tsconfig.json

tsconfig.jsonは、TypeScriptをJavaScriptコンパイルする際のコンパイラオプション等を書いておくファイル。プロジェクトフォルダのルートに置いておくと、コンパイルする際に自動的に見に来てくれる。中身は単純なのだとこんな感じ。

{
    "compilerOptions": {
        "target": "es3",
        "outFile": "ScriptExample.jsx"
    },
    "files": [
        "typings/aftereffects/ae.d.ts",
        "ScriptExample.ts"
    ]
}

ExtendScriptはes3相当なので、targetはes3固定。後、デフォルトだと当然.jsで出力されるので、outFileで.jsxで出力するようにすれば楽。

型定義ファイル

型の恩恵を受けられないとTypeScriptを使う必要がない。型の恩恵を受けるには型定義ファイルが必要だ。 ということで、https://github.com/atarabi/aftereffects.d.tsから型定義ファイルを拾ってこよう。

Visual Studio Code

TypeScriptでの開発はVS Codeが便利。Visual Studio Codeからダウンロード出来る。

使用するTypeScriptを切り替える

デフォルトだとVS Codeに内蔵されたTypeScriptが使用されるので、それを変更する。 Ctrl+Shift+Pを押すとコマンドパレットが開くので、そこでPreferences: Open User Settingsを探す。 選択すると、settings.jsonが開かれるので、そこでtypescript.tsdkの項を追加し使用したいバージョンのTypeScriptのフォルダを指定する。

"typescript.tsdk": "C:/Users/ユーザー名/AppData/Roaming/npm/node_modules/typescript/lib"

のような感じになるはず。

スクランナー

VS Codeには、ビルド等のタスクをエディタ内から楽に実行出来るタスクランナーという機能がある。 この機能を使ってTypeScriptをwatchモードでビルドすることを考える。 Ctrl+Shift+Pを押してコマンドパレットを開き、Tasks: Configure Task Runnerを選択すると、いくつか項目が出るのでTypeScript - Watch Modeを選ぶ。 すると.vscode以下にtask.jsonが生成される。これで、コマンドパレットからTasks: Run Build Taskを選択するか、Ctrl+Shift+Bを押すと さきほど設定したタスクが実行され、tsconfig.jsonの内容に基づき、watchモードでファイル保存のたびに自動でコンパイルしてくれるようになる。

こんな感じだろうか

TypeScript 2.0環境前提。ae-typescript-example

TypeScriptその他

基本的な文法以外。

type guard、型推論まわり

TypeScript 2.0でタイプガード周りが向上したので、下記のように早期リターンがしやすくなった。

(() => {
    const comp = app.project.activeItem;
    if (!(comp instanceof CompItem)) {
        return;
    }
    comp;//上のif文でCompItem以外はreturnしたので、ここはCompItemと推測してくれる!今まではItemと認識されていた。
})();

プロパティに関してもタイプガードしてくれるようになった。

if (app.project.activeItem instanceof CompItem) {
    const comp = app.project.activeItem;//CompItemと認識してくれる!
}

クラスの判定

ExtendScriptにおけるサブクラスといった概念は、JavaScript的に継承してるのではなく、上位と同じメソッド、プロパティを持ってる程度でしかないので 例えばあるレイヤーがAVLayerかどうかをチェックしたい場合は、

if (layer instanceof AVLayer || layer instanceof TextLayer || layer instanceof ShapeLayer) {
    //layer is AVLayer!!
}

のように、AVLayerとそのサブクラスを羅列する必要がある。これを関数化した場合、

function isAVLayer(layer: Layer) {
    return layer instanceof AVLayer || layer instanceof TextLayer || layer instanceof ShapeLayer;
}

のようになるわけだが、これだと型周りの情報が抜けてtrueの場合にAVLayerであると認識してくれない。

if (isAVLayer(layer)) {
    layer;//Layerクラス!!
}

そこで、下記のようにtrueの場合layerの型はAVLayerですよ!と書くと、

function isAVLayer(layer: Layer): layer is AVLayer {
    return layer instanceof AVLayer || layer instanceof TextLayer || layer instanceof ShapeLayer;
}

きちんと解釈してくれる。

if (isAVLayer(layer)) {
    layer;//AVLayerクラス!!
}

polyfill

TypeScriptはes5のpolyfillは用意してくれない(Startupにpolyfillをデフォルトで突っ込んでくれればいいと思うけど)。 for-of文は使えるので、配列周りで面倒なことはそこまでないと思う。

const comp = app.project.activeItem;
if (!(comp instanceof CompItem)) {
    return;
}

const layers = comp.selectedLayers;
const shape_layers: ShapeLayer[] = [];
for (let layer of layers) {
    if (layer instanceof ShapeLayer) {
        shape_layers.push(layer);
    }
}

es6

上記のfor-of文、let, constや、クラス、分割代入、テンプレート文字列等、es6の機能は普通に使えるのでどんどん使っていけばいいと思う。

const comp = app.project.activeItem;
if (!(comp instanceof CompItem)) {
    return;
}
const {name, width, height, pixelAspect, bgColor} = comp;
const [red, green, blue] = bgColor;
comp.layers.addSolid([1 - red, 1 - green, 1 - blue], `invert of "${name}"'s bgColor'`, width, height, pixelAspect);