Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった

@yushimatenjinです。[PlayCanvas](https://playcanvas.jp/)のコードエディターに付属されているリンターが、JSHintという古いものでなおかつカスタマイズができなかったので、`gulp-playcanvas` `playcanvas-node` `playcanvas-cli`と3つのライブラリを作りました。gulpのpluginを作ったのでその知見の共有をさせていただきます。  

## 問題
PlayCanvasを使う上で気になった点が付属のコードエディターのLintが古く具体的には
- constを使うと黄色くなる
- セミコロンを入れないと、赤くエラーが出る
この状況だったので自分のエディターを使って開発をしたい欲が出てきました。

![Image 37](https://user-images.githubusercontent.com/39250588/59757101-8a91ff00-927a-11e9-988f-1f1cd07c3a42.jpg)


## 作ったプラグイン
[gulp-playcanvas](https://github.com/yushimatenjin/gulp-playcanvas)


npmのパッケージとして公開いたしましたので下記のコマンドでインストールできます。

```bash
yarn add gulp-playcanvas
```

ローカル側のgulpfile.jsは`watch`をして、トランスパイルしたものをPlayCanvasにREST APIを使用してアップロードしています。

[examples - hot-reload](https://github.com/yushimatenjin/gulp-playcanvas/tree/master/examples/hot-reload)

#### gulpfile.jsファイル
```javascript
const gulp = require("gulp");
const playcanvas = require("gulp-playcanvas");
const pcOptions = require("./playcanvas.json");
const pug = require("gulp-pug");
const sass = require("gulp-sass");

gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("js", () => {
  return gulp
    .src(["src/**/*.js", "!src/**/_*.js"])
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("sass", () => {
  return gulp
    .src("src/**/*.+(scss|sass)")
    .pipe(sass())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});
gulp.task("default", gulp.parallel("watch"));
```

パッケージのインストール後、`playcanvas.json`という設定ファイルを同じディレクトリに置き、`yarn gulp`コマンドで監視対象を`watch`しておきファイルの中身に変更があった場合に同期をする形で動きます。

```playcanvas.json
{
  "accessToken": "",
  "scenes": [],
  "projectId": "",
  "branchId": "",
  "projectName": "",
  "remotePath": ""
}
```

## 動かした結果

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">6月に2Dゲームを作るというハンズオンをやるのですが、その資料を触ってたらHMR的な動きができたという動画です。 x4倍 <a href="https://t.co/vMHhrCkHkj">pic.twitter.com/vMHhrCkHkj</a></p>&mdash; はが (@Mxcn3) <a href="https://twitter.com/Mxcn3/status/1134057893286703106?ref_src=twsrc%5Etfw">2019年5月30日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Hot Reload + ローカルの`eslint + prettier`で開発ができるのでかなり快適になりました。


タスクは４つの動きをします。

### 1.  監視 
コードの変更をwatch ソースコードの変更を検知しタスクを実行

```javascript
gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});
```
この部分ですね、`pug`, `js`, `sass`に変更があった場合にそれぞれのタスクを実行します。

### 2. ビルド  & 3. アップロード
PugやSassなどをHTML, CSSへ変換し、REST APIを使用しビルドしたコードをアップロードします

```javascript
const pug = require("gulp-pug");
/*
gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
------------自作のプラグインを実行------------------
    .pipe(playcanvas(pcOptions));
------------自作のプラグインを実行--------------------
});
*/
```

[https://github.com/yushimatenjin/gulp-playcanvas/blob/master/index.js#L1](gulp-playcanvas/index.js)


Gulpのプラグインの中身はストリームで流れてくるファイルの情報を使用してアップロードしています。
オプションの渡し方などは、[gulp-ftp](https://github.com/sindresorhus/gulp-ftp)を参考にしました。

```javascript
const PlayCanvas = require("playcanvas-node").default;
const through = require("through2");
const gutil = require("gulp-util");
const path = require("path");

module.exports = options =>
  through.obj(function(file, enc, callback) {
    if (file.isNull()) {
      return callback(null, file);
    }

    if (file.isStream()) {
      return cb(
        new gutil.PluginError("gulp-playcanvas", "Streaming not supported")
      );
    }
    const playcanvas = new PlayCanvas(options);
    playcanvas.updateAssets(
      options.remotePath,
      path.basename(file.path),
      file.path
    );
  
    return callback(null, file);
  });
```

１行目で[playcanvas-node](https://github.com/yushimatenjin/playcanvas-node)という自作のライブラリを読み込んでいるのですが、このライブラリを作ったことで、APIとのやり取りをシンプルに書けてとてもよかったです。

#### 参考リンク
[PlayCanvas.jp](https://playcanvas.com/)
[PlayCanvas/Engine](https://github.com/playcanvas/engine)
[playcanvas-node](https://github.com/yushimatenjin/playcanvas-node)
[playcanvas-gulp](https://github.com/yushimatenjin/gulp-playcanvas)