💞

VSCodeで捗らせる!幸せなCluster Script開発(3:設定編)

2022/11/21に公開約18,600字

連載記事一覧

  1. 全体環境構築編
    VSCode、Node.js、Gitをインストールする。
  2. 個別環境構築編
    VSCodeのワークスペースを作成する。必要なパッケージをnpmでインストールする。
  3. 設定編 <この記事
    VSCodeのtasks.json、launch.jsonを作成する。webpackのwebpack.config.jsを作成する。
  4. コーディング実践編
    予定

こんにちは!かおもです!
前回は各種インストールを行い、個別の環境を準備しました。
今回はインストールした機能を、VSCodeから使用できるようにする設定を行います。
今回の記事の内容を完了すると、ようやく開発に使用できる状態になります。

0:執筆時の環境

本記事の内容は、バージョンが変わると動かないという事がありえるので、現環境を記載しておきます。

  • VSCode 1.73.1
  • Node.js v18.12.1
package.jsonの抜粋
"@clustervr/cluster-script-types": "^1.0.1",
"chai": "^4.3.7",
"mocha": "^10.1.0",
"sinon": "^14.0.2",
"terser": "^5.15.1",
"terser-webpack-plugin": "^5.3.6",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-preprocessor-loader": "^1.2.0"

1:タスクの設定をする

VSCodeのタスクでは何かのプログラムやスクリプトを実行する事ができます。
つまり、前記事でインストールしたwebpack(複数ファイルを1つにまとめる)やterser(ファイルを圧縮する)などを、ここのタスクで実行することができるようになります。

ここでは、以下の3種類のタスクを設定します。

  • テスト用のjsファイルを実行して、テストを行うタスク。
  • 特定のファイルを圧縮するだけのタスク。
  • 複数のjsファイルを一つにまとめるタスク。

それではタスクの設定をしていきましょう。

画面左側にエクスプローラーを表示(ctrl+shift+E)させたら、workフォルダ(前記事で言うところの作業フォルダ)以下に.vscodeという名前のフォルダを作成してください。
フォルダ名の先頭のドット.も必要です。
このフォルダは、VSCodeの各種設定ファイルを保管するための特殊なフォルダになります。

フォルダを作成したら、今度は作成した.vscodeフォルダ以下にtasks.jsonというファイルを作成し、そのファイルを開いてください。

このような状態になっていればOKです。
このファイルにVSCodeのタスクを設定します。

設定するタスクの内容はこちら

長いので、丸ごとコピペして貼り付けてください。
一行目の{と最終行の}は漏れやすいので気を付けてください。
黒枠右上の四角が重なっているアイコンを押すとコピーできます。

tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "テスト",
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "type": "shell",
            "command": "node_modules/.bin/mocha",
            "args": [
                "${file}"
            ],
            "problemMatcher": [],
            "options": {
                "env": {}
            }
        },
        {
            "label": "ビルドのみ",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "type": "shell",
            "command": "node_modules/.bin/webpack",
            "problemMatcher": [],
            "options": {
                "env": {
                    "MINIMIZE" : "false",
                    "IN_FILE": "${file}",
                    "OUT_DIR": "${workspaceFolder:dist}",
                    "OUT_FILE": "${fileBasenameNoExtension}.js",
                    "LOGGING": "true",
                }
            }
        },
        {
            "label": "ビルド+圧縮+log",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "type": "shell",
            "command": "node_modules/.bin/webpack",
            "problemMatcher": [],
            "options": {
                "env": {
                    "MINIMIZE" : "true",
                    "IN_FILE": "${file}",
                    "OUT_DIR": "${workspaceFolder:dist}",
                    "OUT_FILE": "${fileBasenameNoExtension}.compress.js",
                    "LOGGING": "true",
                }
            }
        },
        {
            "label": "ビルド+圧縮+log無し",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "type": "shell",
            "command": "node_modules/.bin/webpack",
            "problemMatcher": [],
            "options": {
                "env": {
                    "MINIMIZE" : "true",
                    "IN_FILE": "${file}",
                    "OUT_DIR": "${workspaceFolder:dist}",
                    "OUT_FILE": "${fileBasenameNoExtension}.compress.js",
                    "LOGGING": "false",
                }
            }
        },
        {
            "label": "圧縮(強)",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "type": "shell",
            "command": "node_modules/.bin/terser",
            "args": [
                "${file}",
                "--compress",
                "ecma=2015",
                "--mangle",
                "--toplevel",
                "--output",
                "${workspaceFolder:dist}\\${fileBasenameNoExtension}.compress.js"
            ],
            "problemMatcher": [],
            "options": {
                "env": {}
            }
        },
        {
            "label": "圧縮(弱)",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "type": "shell",
            "command": "node_modules/.bin/terser",
            "args": [
                "${file}",
                "--compress",
                "ecma=2015",
                "--mangle",
                "--output",
                "${workspaceFolder:dist}\\${fileBasenameNoExtension}.compress.js"
            ],
            "problemMatcher": [],
            "options": {
                "env": {}
            }
        },
    ]
}

1.1:タスクの詳細を確認する

かなり長いファイルですが、細かく分けて把握していきましょう。

冒頭付近のtasks以下のブロックが、それぞれのタスクになります。
例えば、一番先頭のタスクは以下のようになります。
このようなタスクが全部で6個設定されています。

        {
            "label": "テスト",
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "type": "shell",
            "command": "node_modules/.bin/mocha",
            "args": [
                "${file}"
            ],
            "problemMatcher": [],
            "options": {
                "env": {}
            }
        },

各タスクの中で、注目しておきたい項目は以下の4つです。

  • label
  • command
  • args
  • option以下のenv

そして各項目は、
labelという名前のタスクは、
commandのプログラムを実行し、
argsやenvでプログラムの処理に必要な情報を渡す、

という関係にあります。

これをふまえて、各タスクの内容を見ていきましょう。

1.2:label:テスト

名前の通り、テストを行うためのタスクです。
node_modules/.bin/mochaが実行され、${file}が情報として渡されます。

mochaは以前の記事で紹介した通り、テストを行う環境を提供する機能です。
具体的には、mochaの書式に従うことで、JavaScriptでテストの項目を書くことができるようになります。
そしてそのファイルをmochaに渡すことで、書かれたテストの項目が実行され、テストが達成されます。
ここでいうテストとは、関数やクラスが想定している動きになっているかを確認する、というようなイメージで大丈夫です。

${file}はVSCodeが今開いているファイル名(フルパス)に置き換えられて渡されます。

つまりこのタスクは、
今開いているテスト項目のテストを実行する
というタスクになります。

1.3:label:圧縮(強)、圧縮(弱)

このタスクは、圧縮を行うだけのタスクになります。
node_modules/.bin/terserが実行され、以下の情報が渡されます。

圧縮(強)の場合
"${file}",
"--compress",
"ecma=2015",
"--mangle",
"--toplevel",
"--output",
"${workspaceFolder:dist}\\${fileBasenameNoExtension}.compress.js"

terserは以前の記事で紹介した通り、jsファイルを圧縮する機能です。
--outputの後にファイル名を指定することで、圧縮後のファイルの出力先を指定できます。
その他の--compress等のオプションは、圧縮の方法などを指定しています。
詳しくはこちらなどを参照してください。

${file}は先の通り、今開いているファイルになります。
${workspaceFolder:dist}distフォルダ(前記事で言うところのAssets内フォルダ)のフルパスに置き換えられます。
${fileBasenameNoExtension}は今開いているファイルの、拡張子無しの名前になります(パスも付きません)。

分かりづらいので例を上げると、

  • ${file}C:\work\source.js
  • distフォルダがZ:\Unity\Assets\JavaScripts

となっている場合、
上記の${workspaceFolder:dist}\\${fileBasenameNoExtension}.compress.js
Z:\Unity\Assets\JavaScripts\source.compress.jsとなります。

つまりこのタスクは、
今開いているファイルを圧縮し、distフォルダに(圧縮されるならcompressを付けて)出力する
というタスクになります。

1.4:label:ビルドのみ、ビルド+圧縮+log、ビルド+圧縮+log無し

このタスクは、ビルド(ここでは、複数ファイルをまとめて1ファイルにすること)を行うタスクになります。
node_modules/.bin/webpackが実行され、以下の情報が渡されます。

ビルド+圧縮+log無しの場合
"MINIMIZE" : "true",
"IN_FILE": "${file}",
"OUT_DIR": "${workspaceFolder:dist}",
"OUT_FILE": "${fileBasenameNoExtension}.compress.js",
"LOGGING": "false",

webpackは設定の説明が長くなるので結論から言いますと、このタスクは、
今開いているファイルを元にビルドし、distフォルダに(圧縮されるならcompressを付けて)出力する
というタスクになります。

例えば、itemGimmick.jsというファイルが、commonFunc.jsという共通処理のファイルを参照しているとします。
この時は、itemGimmick.jsを開きながらビルドのタスクを実行すると、distフォルダにcommonFunc.js内容を結合したitemGimmick.compress.jsというファイルが出力されます。

1.5:webpackの設定について

webpackは設定が特殊で、渡された情報をそのまま使用していません。
渡された情報をJavaScriptで加工し設定しています。

ここでは、その設定用のJavaScriptファイルを作成します。
root直下にwebpack.config.jsというファイルを作成し、以下の内容をコピペしてください。

コピペ内容はこちら

最終行の};はコピペ漏れしやすいので気を付けてください。

webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = env => {
    return {
        entry: process.env.IN_FILE,
        output: {
            path: process.env.OUT_DIR,
            filename: process.env.OUT_FILE,
        },
        mode: 'production',
        optimization: {
            minimize: process.env.MINIMIZE == "true",
            minimizer: [new TerserPlugin({
              terserOptions: {
                compress: true,
                ecma: 2015,
                mangle: true,
                toplevel: true,
              }
            })],
        },
        module: {
            rules: [
                {
                    test: /\.[mc]?js$/,
                    loader: 'webpack-preprocessor-loader',
                    exclude: /node_modules/,
                    options: {
                        directives: {
                            LOGGING: process.env.LOGGING == "true",
                        }
                    }
                }
            ],
        },
        experiments: {
            //全体を囲う即時実行関数式が消える。
            outputModule: true
        }
    }  
};

このwebpack.config.jsはwebpack実行時に内部で実行され、webpack.config.jsに書かれている関数が出力するオブジェクトを設定として扱います。

webpack.config.js内にprocess.env.XXXXXXと記述されている個所があると思いますが、その個所にタスクから渡される情報が入ります。

順に内容を確認していきましょう。

entry: process.env.IN_FILE,

ファイルの入力に関する設定です。
webpackはentryで指定されたファイルを起点に、他のファイルをまとめます。

output: {
    path: process.env.OUT_DIR,
    filename: process.env.OUT_FILE,
},

まとめられたファイルの出力に関する設定です。
pathのフォルダに、filenameの名前で出力されます。

mode: 'production',

ファイルのまとめ方に関する設定です。
productionの他にdevelopmentというモードがあります。
このモードはデバッグに有用な情報を残すようにしてまとめますが、Clusterでの開発にはあまり使われないと思うので、production固定でよいでしょう。

optimization: {
    minimize: process.env.MINIMIZE == "true",
    minimizer: [new TerserPlugin({
      terserOptions: {
	compress: true,
	ecma: 2015,
	mangle: true,
	toplevel: true,
      }
    })],
},

ファイルをまとめる時に行われる最適化に関する設定です。
MINIMIZEtrueを指定することで、terserを使用した圧縮が行われるような設定になります。
terserのオプションは圧縮のタスクの内容と同じです。

{
    test: /\.[mc]?js$/,
    loader: 'webpack-preprocessor-loader',
    exclude: /node_modules/,
    options: {
	directives: {
	    LOGGING: process.env.LOGGING == "true",
	}
    }
}

読み込まれるファイルをどう扱うかという設定です。
testにマッチしたファイルはloaderで読み込まれ、まとめられるという流れになります。

ここでは、.js、.mjs、.cjsのファイルを読み込むときはwebpack-preprocessor-loaderを使うという設定になっています。
webpack-preprocessor-loaderは、プリプロセッサという処理を行う機能です。
ここでは、そのプリプロセッサ用の値LOGGINGを設定しています。
プリプロセッサについてはこの記事内で後述します。

node_modules以下は変に加工しない方がよいので、excludeで除外しています。

experiments: {
    //全体を囲う即時実行関数式が消える。
    outputModule: true
}

名前の通り、実験的な機能を設定しています。

ここでは、webpackがファイルをまとめる時に、全体を即時実行関数式(IIFE)というもので囲みますが、それを外す設定をしています。
Clusterでは使用できるメモリに制限がありそうなので、その対策として有効かもと考えて設定しています。

2:実行とデバッグの設定をする

長くなりましたが、次に実行とデバッグの設定を行います。

実行とデバッグの機能は、開発したJavaScriptのコードをVSCode上(Node.js上)で実行したりデバッグしたりする機能です。
Cluster向けのJavaScriptはNode.js上で実行してもあまり意味がなさそうですが、デバッグの方はテストと合わせると効果を発揮しそうなので、設定を行います。

.vscodeフォルダ以下に、launch.jsonというファイルを作り、以下の内容をコピペしてください。
一行目の{と最終行の}は漏れやすいので気を付けてください。

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "テストデバッグ",
            "runtimeExecutable": "${workspaceFolder:work}/node_modules/.bin/mocha",
            "program": "${file}"
        }
    ]
}

このファイルも基本的な考え方はtasks.jsonと同じです。
workフォルダにインストールしてあるmochaに、現在開いているファイルを指定して実行する、という内容になります。

3:設定した機能を試してみる

最後に、ここまで設定してきたタスクやデバッグの機能を試しておきます。

3.1:タスクの実行方法を確認する

画面上側メニューからターミナル>タスクの実行とクリックしてください。
ターミナルの表示がない場合は、表示幅が足りずに・・・の中に入っているかもしれません。

タスクの実行をクリックすると以下のような画面になり、これまで登録したタスクが表示されます。

この状態から各タスクを選択することで、そのタスクを実行できます(今は実行しないでよいです)。
escキーなどで、タスクの一覧の画面は閉じることができます。

3.2:デバッグの開始方法を確認する

画面左側の虫と三角のアイコンをクリックするかctrl+shift+Dを押してください。
すると以下のような画面になり、左上に登録したデバッグの機能が表示されます。

ここで、画面左上のテストデバッグ(work)の左側にある右向き三角を押すと、デバッグが開始されます(今は開始しないでよいです)。
ctrl+shift+Eなどで、画面左側を元のエクスプローラーの画面に戻せますので、戻しておいてください。

3.3:お試し用ソースコードを準備する

以下のファイルをworkフォルダの直下に作成してください。
本来であればフォルダを分けるなどして配置した方がよいですが、確認用なのでそのまま作成します。
ここに登場するファイルの細かい内容については、次の記事で触れていく予定です。

ファイル名:scriptableItem01.mjs

scriptableItem01.mjs
const add = (a, b) => {
    const result = a + b;
    return result;
};
const log = (message) => {
    $.log(message);
};

const v = add(1, 2);
log(v);

ファイル名:common.mjs

common.mjs
export const add = (a, b) => {
    return a + b;
};
export const log = (message) => {
    // #!LOGGING
    $.log(message);
};

ファイル名:scriptableItem02.mjs

scriptableItem02.mjs
import { add, log } from './common.mjs';

const v = add(1, 2);
log(v);

ファイル名:common.test.mjs

common.test.mjs
import { expect } from 'chai';
import { add } from './common.mjs';

describe("add", () => {
    
    it("1と2が足されて3が返される", () => {
        const result = add(1, 2);
        expect(result).equal(3);
    });

});


画面左側のエクスプローラーの構成が、同じようになっていることを確認してください。

3.4:「圧縮(弱)」「圧縮(強)」を試す

それでは順にタスクを試していきます。

scriptableItem01.mjsを開き、タスクの圧縮(弱)を実行してください。
画面上側メニューからターミナル>タスクの実行でタスクは実行できます。


するとこのようにターミナルが自動で表示され、ターミナルはタスクで再利用されます、閉じるには任意のキーを押してください。と表示し、タスクが終了します。

ターミナルは右上部分のXボタンで消してもよいですし、ターミナルをクリックして何かキーを押して消してもよいです。そのまま残しておいてもよいです。

タスクが終了すると、distフォルダにscriptableItem01.compress.jsというファイルができています。
dist以下にファイルが表示されていない場合は、distをクリックしますと表示が開かれてファイルが見えます。

ファイルの内容は以下のようになっていると思います。

scriptableItem01.compress.js
const add=(d,o)=>d+o,log=d=>{$.log(d)},v=add(1,2);log(v);

元のファイルと見比べますと、変数名や空白などが省略されかなり圧縮されているのが分かると思います(160byteが56byteになっています)。

タスクの圧縮(強)も試してみましょう。
ファイルの内容は以下のようになっていると思います。

scriptableItem01.compress.js
const o=1+2;var a;a=o,$.log(a);

add関数とlog関数が展開されて削除され、さらなる圧縮がなされました(160byteが31byteになりました)。

3.5:「ビルドのみ」を試す

続いて、複数のファイルを一つにまとめるビルド系のタスクを試してみます。

今度はscriptableItem02.mjsを開いてください。
02のファイルは、01の方にあったadd関数やlog関数がなくなっています。
代わりにcommon.mjsに書かれているadd関数やlog関数を参照しています。
(この参照方法などについては、次回の記事にて触れる予定ですが、詳細を知りたい方はこちらなどをご覧ください)

それではscriptableItem02.mjsを開いた状態で、タスクのビルドのみを実行してください。
distフォルダに以下のようなscriptableItem02.jsができていると思います。

scriptableItem02.js
var __webpack_exports__ = {};

;// CONCATENATED MODULE: ./common.mjs
const add = (a, b) => {
    return a + b;
};
const log = (message) => {
    $.log(message);
};
;// CONCATENATED MODULE: ./scriptableItem02.mjs


const v = add(1, 2);
log(v);

元の02のファイルにはなかったadd関数やlog関数が含まれているので、webpackによりcommon.mjsの内容を参照して一つのファイルにまとめられたことが分かると思います。

3.6:「ビルド+圧縮+log無し」を試す

ビルド+圧縮+logの結果は、01のファイルを圧縮(強)した時と同じになるので割愛します。)

scriptableItem02.mjsを開き、タスクのビルド+圧縮+log無しを実行してください。
distフォルダに`scriptableItem02.compress.jsができていると思いますが、中身がないファイルになっていると思います。
これは、圧縮の過程で、なにも実行する必要がないと判断され、このようになりました。

この結果を説明するために、webpackの設定の時に出てきたプリプロセッサについて触れます。

3.7:プリプロセッサの機能について

プリプロセッサは、ビルドなどを行う前に、ソースコードなどに対して処理を行います。
今回のケースでは、webpackでファイルをまとめる前に、処理を行っています。

webpackの設定の時に作ったwebpack.config.jsには、以下の設定が書かれています。

{
    test: /\.[mc]?js$/,
    loader: 'webpack-preprocessor-loader',
    options: {
	directives: {
	    LOGGING: process.env.LOGGING == "true",
	}
    }
}

ここに書かれている設定が、プリプロセッサの設定です。
directivesの下にあるLOGGINGに注目してください。
ビルド+圧縮+log無しのタスクを実行するときはこのLOGGINGfalseになります。

これをふまえて、common.mjsのファイルを確認してください。

common.mjs
export const add = (a, b) => {
    return a + b;
};
export const log = (message) => {
    // #!LOGGING
    $.log(message);
};

5行目に// #!LOGGINGという記述があります。
プリプロセッサは、このような// #!の行を見つけると、おおよそ以下のような処理を行います。

  1. LOGGINGの値を確認する>LOGGINGはfalse
  2. 値がfalseの場合は(1つ下の)行を削除する。
  3. 値がtrueの場合はなにもしない。

今回は、LOGGINGfalseなので、$.log(message);の行が削除され、webpackに渡されます。
つまり、以下のようなコードが渡されます。

common.mjs
export const add = (a, b) => {
    return a + b;
};
export const log = (message) => {
};

さて、ここから圧縮の処理をイメージしてみます。
このlog関数を見ると、何も実行していないことが分かります。
すると、圧縮時にlog関数の定義やlog関数を呼び出しているところが削除されます。

そこでscriptableItem02.mjsの記述を確認してみると、
add関数の結果はlog関数のみに渡されています。

const v = add(1, 2);
log(v);

すると、log関数が削除されたのであれば、add関数の結果も不要となり、最終的にはadd関数の処理も不要となります。
そして、add関数はここだけ使われているので、add関数の定義も不要となり削除され、圧縮の結果何も残らなくなるのです。

プリプロセッサの詳しい記述方法などはこちらを参照してください。

3.8:「テスト」を試す

最後にテストのタスクを試します。
common.test.mjsを開いてください。

common.test.mjsの内容を簡単に説明すると、
describeはテスト項目のグループで、
itはテストの項目です。
このit内でadd関数が呼び出され、expect(result).equals(3);でadd関数の結果が想定された値かを確認しています。

それではcommon.test.mjsを開いたまま、タスクのテストを実行してください。
以下のように、テストの結果がターミナルに表示されます。

上記のテスト結果の画面は、
add関数についてのテスト項目のグループのうち、
1と2が足されて3が返されるというテスト項目については
テストを通過しました(グリーン表示)
という内容の画面になります。

ClusterのJavaSciprtでは$が存在してるので、このようなテストを行うには工夫(別の記事で触れる予定です)をするか、場合によってはテストが行えないことがあります(こちらの方が多いと思いますが…)。
とはいえ、特定の処理などが正しく動いていることは、結構な安心感につながると思っていますので、積極的にテストが行える環境ができるといいなと思っています。

4:「テストデバッグ」を試してみる

最後にデバッグを試してみます。

common.test.mjsを開いてから、ctrl+shift+Dなどで画面左側に実行とデバッグを表示させます。
このままテストデバッグ(work)の左側の>マークをクリックしてください。
すると、画面下側のタスクバーが一瞬オレンジになり、青色に戻ります。
この状態でworkのターミナルを開き、デバッグコンソールのタブを見ると、先ほどのテスト結果と同じ出力が見られます。

これだけでは、タスクのテストと差がありません。
ですので、デバッグならではの機能を試してみます。

common.test.mjsのコードが表示されている左側に行番号が表示されていると思います。
この行番号にカーソルを合わせると、行番号の左に赤丸が表示されます。
そこで、9行目の行番号にカーソルを合わせ、表示された赤丸をクリックしてください。
すると、カーソルを離しても、以下の画像のように赤丸の表示が残ります。

この状態で再び、画面左上のテストデバッグ(work)の左側の>マークをクリックしてください。
すると、今度は以下のような画面で静止します。

これは、プログラムの処理が、9行目を実行する直前で止まっている事を示しています。
先ほど付けた赤丸は、ブレークポイントといい、デバッグを実行すると、そのブレークポイントの直前でプログラムの処理が停止します。

これのなにが便利かというと、例えば、8行目のresultにカーソルを合わせてもらうと、3と表示されると思います。
つまり、プログラムの途中の状態を確認できるようになります。

普段であれば、$.logなどで途中の状態を出力して確認しているかと思いますが、デバッグで動かすことができるような書き方になっていれば、$.logに頼らずとも途中の状態を確認ができるようになります。

デバッグモードでの操作についてはこちらなどを参照してみてください。
(若干古めの記事ですが、おおよそ同じような動きをしているようなので大丈夫かと思います)

それでは、ブレークポイントで停止しているプログラムを、shift+F5で終了させてください。
終了させると元の画面に戻ります。
ブレークポイントも、再度赤丸をクリックすると元に戻ります。

5:次回、コーディング実践編

次回は、実際にファイルを複数に分割したりする時に必要な書き方や、デバッグ用に必要な工夫などをまとめていきます。

以上です!よいVSCodeライフを!!

Discussion

ログインするとコメントできます