モダンなCEP開発 入門編

# 概要
Adobe CEPのモダンな開発方法をご紹介します。
著者は主にAfter Effectsユーザーなので、After Effects中心の視点でお届けします。

### 想定読者
すでにJSXでスクリプトを開発したことがある人向け。

### 著者について
フリーランスの映像作家。自身で使用する拡張機能を主に開発する。
Web系の開発についてはCEP以外に経験がないため、誤った情報を含む可能性があります。

# JSXスクリプトよりCEP Extensionをおすすめする理由
JSXスクリプトはES3をAdobeが拡張したExtendScriptという言語で実行されます。
ExtendScriptはCC以降アップデートされておらず、モダンなJavaScriptでの書き方(let, const, アロー関数, その他...)は大体できないです。

また、これは著者の感想ですが、Adobeは最近ExtendScriptのサポートが雑で、サポートをやめたそうな雰囲気を感じます。（i.e. Photoshop, Premiere proでのUXP導入など......）
少なくとも、著者はJSXスクリプトでUI構築を行うのはお勧めしません。CEPでやった方がいいと思う。

また、スクリプトはユーザーがバージョンごとにファイルをコピーしなくてはならないのに対し、CEP ExtensionはそのExtensionが指定したバージョンの範囲であれば自動的に読み込まれる点が好ましいです（上に書いたことをすべて吹き飛ばしてこれが一番でかい）。

# 今回解説するもの
CEP開発環境の構築、CEP独特の仕様などを解説していきます。

# 今回解説しないもの
JavaScriptやTypeScript、Node.jsやReactなど言語やライブラリの仕様についてはあまり解説しません。
各自使用するものに合わせて調べるようにしてください。

# 使用するライブラリ
- [Node.js](https://nodejs.org/ja)
- [Bolt-CEP](https://github.com/hyperbrew/bolt-cep)

Node.jsとBolt-CEPを使用してCEP環境をつくります。
VS Codeの拡張機能を用いて開発する方法などもありますが、Bolt-CEPはそのとっつきやすさが魅力です。
`yarn create bolt-cep`したら環境自体は出来上がりです。簡単。

# 環境構築とプロジェクトの作成
上記でも軽く触れましたが、詳しく解説していきます。

### Node.js
各デバイスに合わせてインストールします。（e.g. Windows 64bitなど）
基本的には最新のLTSを選んでおけば問題ないと思います。
今回はyarnも用いるため、併せてyarnもインストールします。
```
npm i -g yarn
```

### Bolt-CEP
Bolt-CEPはインストール不要です。
`yarn create`を用いて直接Bolt-CEP環境をつくります。
```
yarn create bolt-cep
```
するといくつか質問されるので、質問に答えていきます。

<dl>
<dt>Where do you want to create your project?</dt>
<dd>何処にプロジェクトをつくりますか？</dd>

<dt>Which UI framework would you like to use?</dt>
<dd><em>どのUIフレームワークを使いたいですか？</em></dd>
<dd>十字キーでカーソルを移動し、エンターキーで決定します。</dd>

<dt>Which React template would you like to start with?</dt>
<dd><em>どのテンプレートを使いたいですか？</em></dd>
<dd>Reactの部分は上で選んだワークフレームによって変わります。
開発するプロジェクトとは別に`Demo`を作成しておくと、困った際にカンニングペーパーとして使えるのでおすすめです。</dd>

<dt>Which Adobe apps do you want to support?</dt>
<dd><em>どのアドビアプリをサポートしますか？</em></dd>
<dd>十字キーでカーソルを移動し、スペースキーで選択（解除）、エンターキーで決定します。</dd>

<dt>What do you want to use as your panel's display name?</dt>
<dd><em>パネルの表示名は何にしますか？</em></dd>

<dt>What do you want to use as your panel's id?</dt>
<dd><em>パネルのIDは何にしますか？</em></dd>

<dt>Do you want to install dependencies? (recommended)</dt>
<dd><em>依存関係をインストールしますか？ (推奨)</em></dd>
<dd>推奨されてるんでやっときましょう。</dd>
</dl>

<img width=70% src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/775346/c6aeb19e-cca2-4263-9fd8-a36e78eee6df.gif>

環境構築とプロジェクトの作成は以上で完了です。

# 開発
これから開発に入ります。
Bolt-CEPのより詳しい説明はBolt-CEPのReadme.mdにお任せするとして（解説できるほど呑み込めてない）、最低限必要な部分を解説していきます。

## 主に重要なフォルダとファイル
プロジェクトを作成するとフォルダ内にいろいろ入ってるんですが、主に重要なのは以下の通りです。
- src
メインとなるソースコードが入ってるフォルダです。
    - `src/js`
    （以下 CEP JavaScript側 JavaScript側 などと記載）
    JavaScriptに変換されるTypeScriptを入れるフォルダです。
    UIを構築したり、後述する`jsx`内の関数を呼び出したり、基本的な処理を行う部分でもあります。
    - `src/jsx`
    （以下 ExtendScript側 などと記載）
    ExtendScriptに変換されるTypeScriptを入れるフォルダです。
    CEPを用いる場合でもアプリとの直接的な情報のやり取りにはExtendScriptを用いる必要があるので、それらを入れておきます。
    対応するアプリごとにフォルダ分けされていて、それぞれに対応した書き方をする必要があります。
    TypeScriptで書くことができるので、素のExtendScriptを書くより圧倒的に楽です。
    - `src/shared`
    `js`と`jsx`の両方から直接アクセスできるため、Extensionの名前や情報のやり取りに使用する型の宣言などを行います。
    間違っても`js`や`jsx`から`import`しないようにしましょう。循環参照になり、正しく動作しないようになります（二敗）。

- dist
主な出力先です。
`yarn build`を使用してExtensionをビルドしたり、
`yarn zxp`を使用してExtensionを配布できるzxpに圧縮した時、それらが出力される場所です。
- cep.config.ts
Extensionのコンフィグとなるファイルです。
サポートするアプリとバージョンを指定したり、開かれるウィンドウのタイプ（PanelとかModal dialogとか）、Extensionにどのようなパネルが含まれているか、証明書に入力する内容などを指定します。

## より詳細な解説
### cep.config.ts
Extensionの仕様を記載するファイルです。
基本的にはプロジェクトを作成した状態から変更することはありませんが、いくつか変更するべき点があるので解説します。

#### startingDebugPort
複数のExtensionを開発する場合はExtensionごとに変えてください。
zxpに圧縮した後でもポートが占有されます。

#### requiredRuntimeVersion
サポートするCEPの最低バージョンを指定します。CEPバージョンはNode.jsやChromiumのバージョンと連動するため、新しい機能を使用する場合などは正しく指定しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/775346/b1fa6ee9-3636-496c-81af-06342eadfd91.png)
引用: [CEP 12 HTML Extension Cookbook.md](https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_12.x/Documentation/CEP%2012%20HTML%20Extension%20Cookbook.md#chromium-embedded-framework-cef)

#### type
デフォルトのパネルタイプです。
後述する`panels`でも個別に指定することができます。
`"Panel" | "ModalDialog" | "Modeless" | "Custom" | "Embedded"`から選びます。

#### width, height
デフォルトのパネルサイズです。
こちらも`panels`にて個別に指定することができます。

#### panels
パネルごとの設定を追加します。
パネルを増やしたい場合はこちらに追記していくことになります。
```ts
  panels: [
    {
      mainPath: "./main/index.html",
      name: "main",
      panelDisplayName: "Demo",
      // メニューバーのWindow > Extensionから呼び出せるようにするかどうか.
      autoVisible: true,
      width: 600,
      height: 650,
    },
    // 必要であれば設定用のパネルを追加する.
    {
      mainPath: "./settings/index.html",
      name: "settings",
      panelDisplayName: "Demo settings",
      // 設定用のパネルは隠しておく.
      autoVisible: false,
      width: 600,
      height: 650,
    },
  ],
```
パネルを追加する場合は`src/js/`に新たにフォルダを作成し、index.htmlとindex.tsxを追加します。
```
src
 └─ js
    ├─ main
    │   ├─ index.html
    |   └─ index.tsx
    └─ settings
        ├─ index.html
        └─ index.tsx
```

#### build
<dl>
<dt>jsxBin</dt>
<dd>ExtendScript部分をjsxbinに変換するかどうか。</dd>
<dd>配布する際に用いるzxpでは後述のオプションが使用されるため、基本的には`off`でいいと思います。</dd>
<dt>sourceMap</dt>
<dd>src/jsがバンドルされる際、source mapを有効にするかどうか。</dd>
<dd>source mapがないとデバッグが大変なので、基本的に`true`でいいと思います。</dd>
</dl>

#### zxp
配布などを目的としてzxpへ圧縮する際に記入する電子署名の内容とjsxBin, sourceMapの上書きができます。
配布するときのみjsxBinを`on`にし、sourceMapを`false`にするといったことができます。

## デバッグ
今回は私が行っている方法を紹介します（誤りを含む可能性あり）。

### デバッグモードを有効にする
デバッグを行うには、デバッグモードを有効にする必要があります。
1. [ZXP/UXP Installer](https://aescripts.com/learn/zxp-installer/)をインストールし、起動します
2. 画面左上の歯車アイコンをクリックして、設定画面を開きます
3. 設定画面左下のアイコンをクリックします
4. 使用するCEPバージョンの`Enable debugging`にチェックマークを入れます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/775346/dd7ed8b8-21d9-4ce2-9a45-54df27fabe56.png)

### デバッグ手順
1\. まずはプロジェクトフォルダでプロンプトを起動し、プロジェクトをビルドします
```
yarn build
```

2\. 開発サーバーを起動します
```
yarn dev
```

**`yarn dev`**
HMR Hot-reloadingと開発モードで起動します。
JavaScriptとExtendScriptに変更があればリビルドされ、自動的に更新されます（ホットリロード）。
`cep.config.ts`の`port`で指定したlocalhostのポートを通じて、Adobeアプリのパネル上で表示されるようになります。

3\. アプリを起動してメニューバーのWindow > ExtensionsからExtensionを起動します

4\. `cep.config.ts`の`startingDebugPort`の値でlocalhostにアクセスすればDevToolsを開くことができ、コンソールやエラーの内容を見ることができます。

::: note warn
複数のアプリをサポートする場合は、アプリごとにDebugPortが異なるので注意してください。
`./dist/cep/.debug`を見ればどのアプリがどの番号を使用しているか見ることができます。
:::

mainパネル以外のコンソールやエラーを見るにはDevTools内で`localhost:3000/settings/index.html`など目的のパネルにアクセスする方法もありますし、`dispatchTS`などのパネル間通信を用いて、`console.log`などはすべてmainパネルで行うという方法もあります。

## 独特の書き方について
CEP Extensionでの独特な書き方について解説します。

### ExtendScriptの呼び出し方
ExtendScriptをCEP JavaScript側から呼び出す場合は`src/jsx`から`import`したりはせずに、`eval**`という関数たちを用います。

#### `evalTS`
ExtendScript側で定義された関数を呼び出すことができます。
なんかすごい仕組みにより.d.tsを記述せずとも補完が生きているので、容易に記述することができます。

- CEP JavaScript
```ts
const res = await evalTS("myFunc", "test");
console.log(res);

evalTS("myFuncObj", { height: 90, width: 100 }).then((res) => {
  console.log(res.x);
  console.log(res.y);
});
```
- ExtendScript
```ts
export const myFunc = (str: string) => {
  return str;
};

export const myFuncObj = (obj: { height: number, width: number }) => {
  return {
    y: obj.height,
    x: obj.width,
  };
};
```
ExtendScriptからの戻り値はすべて`Promise`になっています。

#### `evalES`
ExtendScriptを文字列として受け渡して実行することができます。
テンプレートリテラルを使用することで、実質的に情報を渡すこともできます。
第二引数を`true`にすることでグローバルスコープの関数を呼び出すことにも使用できます。
```ts
evalES(`helloWorld("${csi.getApplicationID()}")`);
evalES(
  `alert("Hello from ExtendScript :: " + app.appName + " " + app.version)`,
  true
);
```

基本的には`evalTS`を使用し、`alert`など、簡単な横着をするときのみ`evalES`を使用するのがいいと思います。

### ExtendScript側からCEP JavaScriptを呼び出す
ExtendScriptが時間を要する場合、CEP JavaScript側に進捗を伝えたりしたい場面もあるでしょう。
基本的に、時間を要する様な複雑な数値計算などはCEP JavaScript側で行うべきです。
ただ、After Effectsで複数レイヤーを追加する場合など、最適化しても時間がかかる場合もあるので紹介します。

**1. イベントの宣言**
`shared/universals.ts`の`EventTS`でイベントタイプを宣言します。
```ts
export type EventTS = {
  myCustomEvent: {
    oneValue: string,
    anotherValue: number,
  },
  // [... other events]
};
```
**2. CEP JavaScript側**
`listenTS`に関数を登録することで、イベントを受け取ることができます。
```ts
import { listenTS } from "../lib/utils/bolt";

listenTS("myCustomEvent", (data) => {
  console.log("oneValue is", data.oneValue);
  console.log("anotherValue is", data.anotherValue);
});
```
**3. ExtendScript側**
`dispatchTS`を呼び出すことでイベントを発信できます。
```ts
import { dispatchTS } from "../utils/utils";

dispatchTS("myCustomEvent", { oneValue: "name", anotherValue: 20 });
```
それはそれとして、`dispatchTS`はパネル間通信にも用いることができます。
パネル間通信には`vulcan`を用いることもできますが、`vulcan`は`dispatchTS`に比べると記述が長くなりやすいです。

### パネル間通信
前述した`dispatchTS`を用いる方法と、`vulcan`を用いる方法があります。
こちらでは`vulcan`について解説する予定でしたが、`dispatchTS`があまりにも`vulcan`の上位互換だったため省略します。
軽く触れておくと以下のように記述します。
```ts
import {vulcanSend, vulcanListen} from "../lib/utils/bolt"

// 発信側. 渡すオブジェクトにeventが必要なため冗長になりやすい.
// wrapperを書けば解決できる.
vulcanSend("event", {event: "event", payload: { oneValue: "name", anotherValue: 20 }});

// 受け取る側. payloadの型はanyになってしまうので, 指定が必要.
vulcanListen("event", (res: {payload: { oneValue: string, anotherValue: number }}) => {
    console.log(res.payload.oneValue);
})
```

### モジュールを使いたいときの注意点
Node.jsの組み込みモジュールは普段通り`import`することができません。
Node.jsの組み込みモジュールは`src/js/lib/node.ts`から`import`できます。
詳しくは[こちら](https://github.com/hyperbrew/bolt-cep?tab=readme-ov-file#troubleshooting-modules)を参照してください。

## 初めてHTMLやNode.jsを扱う方向け
JSXからCEPへの移行で、初めてHTMLやNode.jsを触るという方向けに、少しだけ解説します。
詳しくは調べたほうがわかりやすいと思いますので、「調べ方も解らない」を解消するためにすこしだけ。

### UIについて
ScriptUIのUIを再現したい場合はこちらのサイトでUIを構築することができます。
[ScriptUI Dialog Builder](https://scriptui.joonas.me/)
UIを作った後ExportをすればHTMLを入手できます。

### ワークフレームについて
自分の好みで選んで大丈夫です。私はReactを使っています。
React, Vue, Svelteを比較する記事を読んだり、構文を見たりして、最後は好みで決めてください。

UIを自分でデザインする場合、CSSを直に書くよりはSassかTailwind CSSがおすすめです。

#### Sass
Bolt-CEPのプロジェクトを作った段階でインストールされているので、とっつきやすいです。
SassはあくまでCSSを拡張しただけって感じで、あまり独特の記述が必要ないのもうれしいです。
ネストできたり変数が使えたり（モダンなブラウザではCSSでできるという話もありますが）、痒い所に手が届くようになる感じです。

#### Tailwind CSS
めちゃくちゃ独特の記述が必要ですが、ClassNameを考える必要がなかったり、コンポーネント（UIの構成要素）ごとにスタイルをインラインで記述できるので、慣れればかなり楽です。
執筆時点で最新版は4ですが、Viteなど開発環境の兼ね合いでうまくインストールできたことがないので、3をおすすめしておきます（入門書を書く人間にあるまじき姿勢）。

# 配布
配布する際はzxpというzipベースの形式に圧縮します。
```
yarn zxp
```
このコマンドによって`./dist/zxp`に.zxpが作成されます。
これをユーザーは[ZXP/UXP Installer](https://aescripts.com/learn/zxp-installer/)などを用いてインストールすることになります。

# まとめ
Bolt-CEPを用いたモダンなCEP開発についてご紹介しました。
概ね入り口として書くべきことは書けたと思います。
ご質問やご指摘があればコメントよろしくお願いします。

## 参考文献
記事を書くにあたり参考にしました。
https://github.com/Adobe-CEP/CEP-Resources
https://github.com/hyperbrew/bolt-cep

# 本日のオチ
ExtendScriptをTypeScriptで書く方法もあります。
CEPが面倒だったり、UIが不要な簡単なスクリプトであれば、こちらで開発するのがおすすめです。

[[ExtendScript] After Effects用のScriptをTypescriptで開発する](https://zenn.dev/shimarisu_121/articles/2b9d67fad9c637)
by. shimarisu_121 様