Home About Contact
JavaScript , Node.js , ExtendScript

Node.js で ExtendScript する話(パート1)

Web関係のJavascript 開発では、Node.js を使って モジュールに分割して開発をしていく。 これを InDesign 用の ExtendScript でもできるようにしたい。 もちろん、ExtendScript にも @include 記述により、モジュールを使うことはできる。 しかし、その場合できあがったコードを配布するときに、依存しているモジュールファイルも一緒に渡す必要が生じる。 これを避けたい。配布時には単にひとつのファイルだけで完結したい。

ESM と webpack を使う

まず、ExtendScript のことは考えないで、Javascript の世界において標準の モジュール管理方法を把握しよう。 調べてみると、CommonJS や ESM(ECMAScript Modules) などの方法があるらしい。 しかし、2021年においては、ESM が主流のようなので、これを使おう。

ESM については developer.mozilla.org のページ を 読めばおおむね把握できる。

そして、この ESM 方式により複数のコードを一つのコードにまとめる方法は、Node.js + webpack を使うのが主流らしい。 このエントリーでは、いちばん基本のこの方法を試します。

なお、試行錯誤の結果、最終的には webpack ではなく rollup を使うことにした。その件はパート2にまとめました。

sayhello プロジェクト

それでは Node.js でモジュールを使ったコードをつくっていく。 ここでは、sayHello と sayKonnitiwa という 2つのfunction を持ったモジュールを export し、メインのコードからこれらを import して使うことにする。

ここで使用するツールは Node.js と Make でそれぞれバージョンは以下の通り:

$ node -v
v12.16.2

$ make -v
GNU Make 3.81

それでは、プロジェクトとディレクトリを作成し、必要なツールをインストールする:

$ mkdir sayhello
$ cd sayhello
$ npm init -y
$ npm install webpack@5.52.0 --save-dev
$ npm install webpack-cli@4.8.0 --save-dev

ここまでのところで、一旦 package.json を確認します:

{
  "name": "sayhello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.52.0",
    "webpack-cli": "^4.8.0"
  }
}

方針は以下のようにします。

この方針にしたがって、webpack の設定を先にしておきます。 webpack では webpack コマンドを実行したときに webpack.config.js を読む、という暗黙のルールがある。

webpack.config.js :

module.exports = {
	mode: 'production',
	entry: './main.js',
	output: {
		path: __dirname,
		filename: 'main.jsx'
	}
};

ここでは、単に main.js から main.jsx を生成するよ、という設定をしているだけです。 モジュールに関する指定はなく、webpack が main.js 内に設定されているモジュールを読みとって、いいかんじに一つにまとめてくれるようです。

それではコードを書きます。

js_modules/say.js :

var sayHello = function(name){
	return 'Hello, ' + name + '!';
};
var sayKonnitiwa = function(name){
	return 'こんにちは ' + name + '!';
};
export { sayHello, sayKonnitiwa };

エクスポートした sayHello, sayKonnitiwa 関数を使うメインのコード main.js:

import {sayHello,sayKonnitiwa} from './js_modules/say.js';

console.log( sayHello('Taro') );
console.log( sayKonnitiwa('Hanako') );

ここまでで、ディレクトリ構成を確認しておきます:

.
├── js_modules
│   └── say.js
├── main.js
├── node_modules/
├── package-lock.json
├── package.json
└── webpack.config.js

それでは webpack コマンドを実行して、main.js と js_modules/say.js をひとつにまとめた main.jsx を生成します。

webpack コマンドは ./node_modules/.bin/webpack にインストールされているので:

$ ./node_modules/.bin/webpack 

とすれば、webpack.config.js の内容にしたがって main.jsx が生成されます。

$ node main.jsx
Hello, Taro!
こんにちは Hanako!

うまくいきました。

ExtendScript として作動するように改造する

ExtendScript には console.log が無いのでそれを用意します。

main.js :

import {sayHello,sayKonnitiwa} from './js_modules/say.js';

var console = {};
console.log = function(message){
	$.writeln(message);
};

console.log( sayHello('Taro') );
console.log( sayKonnitiwa('Hanako') );

webpack コマンドを実行して・・・ですが、面倒なので、Make で処理できるように Makefile をつくります。

main.jsx: main.js js_modules/say.js
	./node_modules/.bin/webpack
	echo "//@target InDesign" >> $@

clean:
	$(RM) main.jsx

VSCode + ExtendScript Debugger で InDesign を使ってこのコードを実行するために、 @target InDesign を main.jsx の末尾に追加しておきます。

make を実行して、先ほどの変更を反映しておきます:

$ make main.jsx

さらに VSCode + ExtendScript Debugger で InDesign を使ってこのコードを実行するために、 .vscode/launch.json を追加:

{
    "version": "1.0.0",
    "configurations": [
        {
            "type": "extendscript-debug",
            "request": "launch",
            "name": "main.jsx",
            "program": "${workspaceFolder}/main.jsx",
            "stopOnEntry": false
        }
    ]
}

いろいろファイルを追加したので、現在のプロジェクトディレクトリ状況を確認:

.
├── Makefile
├── js_modules
│   └── say.js
├── .vscode
│   └── launch.json
├── main.js
├── main.jsx
├── node_modules/
├── package-lock.json
├── package.json
└── webpack.config.js

これで準備が整ったので、 code . して VSCode を起動して、InDesign で main.jsx をデバッグ実行してみます。

main.jsx NG

・・・エラーです。 webpack により生成された main.jsx の先頭部分の記述に問題があるようです。

(()=>{

こういう書き方は ExtendScript は対応していないので、手動で書き直します。

(function(){

これで再度実行。

main.jsx OK

うまくいきました。

しかし、生成対象の main.jsx を毎回手作業で直すのは面倒です。 パート2 ではこの問題を回避する方法を考えます。