typescriptでExtendScriptを書いてみた

# AEのスクリプトをtypescriptで書くには

普段はpythonでゴリゴリツールを書いたりしているのですが、紆余曲折を経てtypescriptでAfterEffectsのスクリプトを書くにはどうしたらいいかをちょっとまとめてみました。

## ExtendScriptとは

ExtendScriptとは、adobeの作ったECMA Script(ES3)準拠の独自言語。
adobe製品で使用できるスクリプト言語になります。

拡張子がjsx(Reactの方面のやつではない)、jsxinc、とjsxbinの3つがあります。
jsxとjsxincは内容に違いはなく、アプリケーション側でメニューに表示されるかされないかの違いのみになります。
jsxincはアプリケーション側でメニューに出ないため、クラスなどをこちらに書いておくと便利です。
jsxbinはESTKというExtendScriptを書くアプリケーションで、jsxファイルを暗号化して出力したファイルに付く拡張子です。


## ExtendScriptを普通に書く

ExtendScriptはES3準拠ということで、クラスなどを書く際に結構大変というかあまり見慣れない書き方をしないといけないのと、継承も結構大変で、スコープも難しいのでややこしくなりがちです。

```js:MyClass.jsxinc
var MyClass = (function(){
  function MyClass(){
    //constractor
  }

  var p = MyClass.prototype;
  p.func1 = function(){
    //method
  }
  return MyClass;
}());
```

```js:Main.jsx
(function(){
  #include 'MyClass.jsxinc'
  
  var mc = new MyClass();
  mc.func1();
}());
```

しかし！、ExtendScriptには#includeというCommonJSのrequireのような機能が付いているのです。クラス側にexportと書く必要もなく、利用する側も#include 'MyClass.jsxinc'でファイルを読み込むことができてとても便利。クラスであればnewでインスタンス化、単に関数の書いてあるものを読み込めばそれを使うことができます。namespace的なものは無いのでクラス名などは重複しないようなものを付けないといけないと思いますが。


## typescriptを使ってみる

typescriptでは静的型付けや、それによるエラーチェック等の便利なものもあるので、これでExtendScriptを書くと便利なのではということで、typescriptでExtendScriptを書いてみることにします。

まずは必要なものをインストールします。typescriptはvscodeを使用するのが良さそうでしたので以下のような構成にしてます。

* node.js 6.11
* vscode

pythonでいつも仮想環境を使って、作ってるものに合わせて環境を変えているので、npmでのパッケージインストール時に ``-g``オプションを使用したくないのですが、vscodeはタスクを構成するファイルであるtasks.jsonに記述されたコマンドをnode_modules/.bin内のものを使ってくれるので便利。nodeも仮想化しないといけないのかと思いましたが、何か他のツールの追加インストール無しで出来るこの機能はとてもありがたいです。

まずは作業フォルダを作成して、その中にpackage.jsonを生成します。

```cmd
npm init -y
```

追加のツールのインストールはありませんが、npmでインストールしないといけないものはいろいろあります。ということでnpmでtypescriptをインストールします。

```cmd
npm install typescript --save-dev
```

typescriptはtscというコンパイラでtsをjsにコンパイルするので、まずそのコンパイル設定を決めるtsconfig.jsonを生成します。typescriptは-gでインストールしていないのでtscはnode_modules内にあります。なので最初はそのパスまでのコマンドを実行します

```cmd
node_modules/.bin/tsc --init
```

これでtsconfig.jsonができました。

tscではコンパイルするjsのターゲットを選ぶことができます。ExtendScriptはES3準拠なのでtsconfig.json内の"target"をes3に変更しておきます。


## タスクの構成を設定する

Ctrl+Shift+PでメニューをだしてTaskと入力してみると一覧にタスクの構成というのが出てきます。

これを選ぶと、tsconfig.jsonがある場合、tscを使ってコンパイルをする設定としてtscでビルドとtscでウォッチというのが出てきます。tscでビルドを選択すると、tsconfig.jsonの設定に沿ってコンパイルされたjsが書き出されます。tscでウォッチの場合は、tsファイルに変更があると自動でコンパイルをしてくれるようになります。この場合のタスクは別プロセスで常に監視をしているので、監視を終了する際にはタスクの終了を選ぶと監視を終了できるようになります。

## AfterEffects用の型定義ファイルを使用する

typescriptには型定義ファイルというのがあります。これのAE版を有志のかたが作成されているので、こちらを使用してコード補完が利くようにします。

![atarabi/aftereffects.d.ts](https://github.com/atarabi/aftereffects.d.ts)

これをダウンロードして、適当な場所に配置して、tsファイルにファイルの場所を書くとコード補完が利くようになります。

## 単一のファイルを作成してみる

まずは適当なtsファイルを書いて、それをコンパイルしてESTKで実行してみます。

```ts:firstProgram.ts
/// <reference path="./aftereffects.d.ts/ae.d.ts"/>

(() => {
    let items = [];
    for (var i = 1; i <= app.project.numItems; i++) {
        const item = app.project.item(i);
        if (item instanceof CompItem) {
            items.push(item);
        }
    }
    alert(items);
})();
```

これをタスクの実行からtsc:ビルドを実行すると、コンパイルされたjsが書き出されます。

```js:firstProgram.js
"use strict";
(function () {
    var items = [];
    for (var i = 1; i <= app.project.numItems; i++) {
        var item = app.project.item(i);
        if (item instanceof CompItem) {
            items.push(item);
        }
    }
    alert(items);
})();
```

このjsをESTKで開いて、AEをターゲットにして実行すると。無事実行されました。

## ファイルが複数ある場合

さて、普段通り別ファイルにして利用してみます。とりあえず上記のファイルをクラスではなく、普通に分割してみます。

```ts:GetComps.ts
export function getComps() {
    let items = [];
    for (var i = 1; i <= app.project.numItems; i++) {
        const item = app.project.item(i);
        if (item instanceof CompItem) {
            items.push(item);
        }
    }
    return items;
}
```
```ts:secondProgram.ts
/// <reference path="./aftereffects.d.ts/ae.d.ts"/>

import comp = require('./GetComps')

(() => {

    alert(comp.getComps());

})();
```

エラーも出ていないようなので、タスクを実行からtsd:ビルドを実行します。

```js:GetComps.js
"use strict";
exports.__esModule = true;
function getComps() {
    var items = [];
    for (var i = 1; i <= app.project.numItems; i++) {
        var item = app.project.item(i);
        if (item instanceof CompItem) {
            items.push(item);
        }
    }
    return items;
}
exports.getComps = getComps;
```

```js:secondProgram.js
"use strict";
exports.__esModule = true;
var comp = require("./GetComps");
(function () {
    alert(comp.getComps());
})();
```

別々にコンパイルされたファイルができました。
このままESTKでsecondProgram.jsを開いても、exportsが未定義になるので実行できません。
tscのコンパイル設定で、typescriptを1つのファイルにするというものがあるのですが、これで1つのjsにしても、同じように未定義の関数が実装されてしまい実行できません。

ExtendScriptの#includeのなんと便利なことか。

しかしこれはbrowserifyというもので解決することができます。試しにbrowserifyをインストールしてみましょう。

```cmd
npm install browserify
```
```cmd
node_modules\.bin\browserify  secondProgram.js -o bundle.js
```

bundle.jsというものができました

```js:bundle.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
exports.__esModule = true;
function getComps() {
    var items = [];
    for (var i = 1; i <= app.project.numItems; i++) {
        var item = app.project.item(i);
        if (item instanceof CompItem) {
            items.push(item);
        }
    }
    return items;
}
exports.getComps = getComps;

},{}],2:[function(require,module,exports){
"use strict";
exports.__esModule = true;
var comp = require("./GetComps");
(function () {
    alert(comp.getComps());
})();

},{"./GetComps":1}]},{},[2]);
```

これをESTKで開いてAEをターゲットにして実行してみると、無事実行できました。

ということでtypescriptを用いて今までと同じように書くことができる環境ができたので、typescriptにシフトしていっても問題なさそうな感じがします。
ただしデバッグはESTK上でしか出来ないのと、jsのコード位置がts上のどこに当たるかがちょっとわかりにくいので若干デバッグはしづらそうな感じがします。
コンパイルされたjsは結構読みやすいので、browserifyを使用せずに、ExtendScriptで読めるようにexportやrequire部分などを置換するツールを書いたほうがいいような気もします。

ということでひとまずはtypescriptでExtendScriptを同じように書くというのはできました。
つぎはこのコンパイルと結合を自動化するようにしたいと思います。