TypeScriptでWSH(Chakra)を書く(2020年版)

#概要

WSHのJScriptは大変古いES3相当のJavascriptですが、Windows10ではEdgeで使われているChakraエンジンを指定することでES2015(ES6)相当のJavascriptをWSHで使うことができるようになっています。

この環境なら制限の多いES3とは違いES2015なのでTypescriptでも十分現実的にコーディングできるのではないかということでやってみました。

#インストール

Typescriptでコーディングするなら型定義ファイルがないと話になりませんが、さすがにどちらもMicrosoft製なので用意されていました。

```
npm install @types/windows-script-host
```

これでWSHの型定義ファイルがインストールされますが、これにはWSH使うなら避けて通れないであろう各種COMオブジェクトの型定義が入っていないので、必要に応じてインストールします。(activex-interopとactivex-iwshruntimelibraryはwindows-script-hostインストール時に一緒にインストールされるようです)

[npmパッケージ一覧](https://www.npmjs.com/search?q=%40types%2Factivex)から探すと便利です。

#Typescript設定

WSHへのトランスパイルでは2つ注意点があり、libの設定をデフォルトにするとTypescriptのScriptHostライブラリと競合してトランスパイルに失敗するので必ずtargetと同じバージョンのライブラリだけを指定すること、moduleはnoneに設定する必要があります。
モジュールは使えませんが、そもそもCOMオブジェクトを扱うのが中心でWSH向けのモジュールはほとんど存在しないのでまず問題にはなりません。
(もしかするとAMDやUMDならいけるかもしれませんが未確認です、Webpack等のモジュールハンドラを使う場合は話が変わりますがこの記事では扱いません)

tsconfig.jsonを使う場合は下記の設定でトランスパイルできます。

```tsconfig.json
{
  "compilerOptions": {
    "target": "es2015",
    "module": "none",
    "lib": ["es2015"],
    "strict": true,
  }
}
```

#コーディング

WSHでは.jsファイルをダブルクリックで起動すると古いJScriptエンジンで実行するため、
[Chakraエンジン対応のWSHスクリプトをダブルクリックで実行できるようにする
](https://qiita.com/riafeed/items/fcedc5943e6a041c90d7)という記事でダブルクリックでもChakraエンジンで起動するようにしましたが、このVer.2の内容をTypeScriptで実装するとこうなります。

```test.ts
/*@cc_on
@if (1)
var sh = WScript.CreateObject("WScript.Shell");
var fso = WScript.CreateObject("Scripting.FileSystemObject");
var ads = WScript.CreateObject("ADODB.Stream");

ads.charset =  "UTF-8";
ads.Open();
ads.LoadFromFile(WScript.ScriptFullName);
var text = ads.ReadText(-1);
ads.Close();

var outf = fso.GetFile(WScript.ScriptFullName).ParentFolder.Path + "\\_" + WScript.ScriptName;

ads.charset = "Shift-JIS";
ads.Open();
ads.WriteText(text, 1);
ads.SaveToFile(outf, 2);
ads.Close();

var command = sh.ExpandEnvironmentStrings("%windir%") + "\\System32\\wscript.exe //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} " + outf;

sh.run(command, 1, true);

fso.DeleteFile(outf);

WScript.Quit();

@end
@*/
//@if (0)
class Test {
  private name : string;

  constructor(name : string) {
    this.name = name;
  }
 
  output() {
    return 'Hello ' + this.name;
  }
}
  
class ExTest extends Test {
  output() {
    return  super.output() + '!!';
  }
}
  
var test = new Test('わーるど');
WScript.Echo(test.output()); //Hello わーるど

var extest = new ExTest('わーるず');
var sh = WScript.CreateObject("WScript.Shell");
sh.Popup(extest.output()); //Hello わーるず!!

//@end
```

これを先ほどの設定でトランスパイルするとそのままダブルクリックで実行できるWSHスクリプトが完成します。

# gulp等のタスクランナーでトランスパイル後のコードをShift-JISに変換すればいいのでは…

タスクランナーのgulpにはgulp-convert-encodingなるプラグインがあるようなので、Typescript使うなら上記Ver2で呼びなおしの際に変換するよりもこのプラグインで変換したほうがいいに決まってますよね…

参考: [gulp：ビルド時にcharsetやファイルエンコードをShift_JISに変換する方法](https://www.nxworld.net/services-resource/gulp-shift-jis-replace-and-convert-encoding.html)

#インテリセンス

@types/windows-script-hostをインストールしていればWScript.CreateObject()でCOMオブジェクトを作っても対応する適切な型を推測してコード補完してくれる仕組みになっているようです。
ただし、型定義に存在しないCOMオブジェクトを指定するとエラーになってトランスパイルできません。
なので繰り返しになりますが、Microsoft OfficeなどのWSHとは本来無関係のCOMオブジェクトを使う場合は対応した型定義をインストールする必要があるので、[npmパッケージ一覧](https://www.npmjs.com/search?q=%40types%2Factivex)で探してみてください。

どうしても型定義が存在しないCOMオブジェクトを使いたい場合は以下のように引数をanyにキャストします。当たり前ですがコード補完は効きません。

```typescript
var ie = WScript.CreateObject("InternetExplorer.Application" as any);
```


#ChakraとJScriptの相違点(メモ)

* new ActiveXObjectは使えません。代わりにWScript.CreateObjectを使います。
* なぜかWScript.Quitは使えません(型定義には入っていますが)。代替方法は基本的にないようです。
* 条件付きコンパイルは一切使えません。JScriptとChakraの判別で有難く使ってますがそれ以外ではJscript独自仕様でぶっちゃけ不要なので問題ないです。

#参考

[[Office]TypescriptとWebpackを使ってWSHを書いてOfficeを操作する](https://qiita.com/stm32p103/items/a2c125bcdb9899453746)
[TypeScriptでWSHを書いちゃおう](https://takanosho.wordpress.com/2012/12/24/visual-studio-advent-calendar-2012/)