Gulp+Sftp+BrowserSyncでリモートサーバへのPush+リロードを自動化させる

# はじめに
表題の通りですが、前々からこういったことがやりたいと思っていました。今回Gulpを使うことで簡単に実現できた気がするので、メモとしてまとめました。何かのお役にでも立てば幸いです。

**[目次]**

- やりたいこと
- メリット
- デメリット
- 用意するもの
- ファイル構成
- 環境構築の流れ
- 実行してみる
- 終わりに


# やりたいこと
1. ローカルでファイルを修正
2. 同時にリモートサーバへ修正したファイルをアップロード
3. そのまま該当サイトを表示したブラウザを自動リロード


# メリット
- ローカルにリモートと同じ環境を再現する必要がない
- リモートサーバ側で何か設定する必要がない
- 好きなエディタでファイルの修正ができる（エディタ独自の機能を使わない）
- SFTPでファイル送信しているだけなので、いつどんなタイミングでも始める・止めることができる
- Gulpなのでプリプロセッサやテンプレートと自由に組み合わせることができる
- BrowserSyncを使っているので複数ブラウザでもOK


# デメリット
- gulpの導入が必要
- 同名ファイルは上書きされるのでやや破壊的な操作になる（特にPATHの指定には注意）


# 用意するもの
- gulpが実行できる環境
- パッケージ: `gulp-sftp`
- パッケージ: `browser-sync`
- リモートサーバの接続情報をまとめたjsonファイル
- その他やりたいことに応じたパッケージなど

> - ftp接続の場合は、`gulp-ftp`とかでも良いかもしれません
> - また動作は以下のVersionで確認しました（2016-03-08現在

|名前|Version|
|:--|:--|
|node.js|4.2.1|
|gulp|3.9.1|
|gulp-sftp|0.1.5|
|browser-sync|2.11.1|


# ファイル構成

```
┬─ .sftpconfig.json
├─ gulpfile.js
├─ html/ // アップするファイルのドキュメントルート
├─ package.json
└─ node_modules/
```



# 環境構築の流れ

## パッケージのインストール
まずはGulpとパッケージをインストールします

```
$ cd ../作業ディレクトリ/
$ npm init
$ npm install --save-dev gulp
$ npm install --save-dev gulp-sftp
$ npm install --save-dev browser-sync
```

## 必要なファイルの作成
タスクを定義するgulpfile.jsとリモートサーバの接続情報を記載した.sftpconfig.jsonを作成します。jsonのファイル名や格納場所は何でも良いと思います（gulpfile内で指定しているため※後述）。当然ですが素のID/PWを記載する場合は扱いに注意しましょう。

```.sftpconfig.json
{
  "host": "192.168.33.40",
  "port": 22,
  "user": "vagrant",
  "pass": "vagrant",
  "remotePath": "/var/www/html/public_html/php/test/"
}
```

その他オプションや秘密鍵の設定などは[gulp-sftpのドキュメント](https://www.npmjs.com/package/gulp-sftp)を確認ください


> ここではリモートサーバ=vagrantとして動作を試しました。なおvagrantであればsync_folder機能が使えるので本来Sftpは不要ですが...


## gulpfile.jsにタスクを定義
ここでは主に以下のタスクを定義します。

- **gulp default**: Proxy Serverのbuildとファイルの監視→Sftp+リロード
- **gulp sftp:all**: 指定ディレクトリ以下をまとめてSftpアップロード

```gulpfile.js
var gulp = require('gulp'),
    fs   = require('fs'),   // node.js api
    path = require('path'), // node.js api
    sftp = require('gulp-sftp'),
    bs   = require('browser-sync').create();

/**
 * ファイルを読み込むための関数
 * @param  {string} 読み込みたいファイル（パス指定
 * @return {object} ファイルの中身を返す
 */
var loadJsonSync = function(filename) {
  return JSON.parse(fs.readFileSync(filename, 'utf8'));
};

/* 基本設定 */
var base = {
      srcDir: 'src/',  // 今回のサンプルでは不使用
      distDir: 'html/'
    },
    conf = {
      // SFTPを発動させる対象
      watchPath: [
        base.distDir + '**/*.html',
        base.distDir + '**/*.php',
        base.distDir + '**/*.css',
        base.distDir + '**/*.js'
        // 対象から外す記述サンプル
        // '!' + base.distDir + '**/*.html'
      ],
      sftp: {
        // サーバ情報を記載したjsonファイルのパスを指定
        server :'./.sftpconfig.json',
        // sftp:allタスクを使用するときにアップする対象
        localPath: [
          base.distDir + '**'
        ],
      },
      browserSync: {
        proxy: {
          proxy: '192.168.33.40',
          port: 9999,
          open: false,
          notify: false
        }
      }
    };

/**
 * 変更ファイルのアップ+リロードタスクを実行する関数
 * @param {string} localPath アップロードするファイル
 * @param {string} remotePath アップロード先（ティレクトリ）
 */
var uploadAndReload = function(localPath, remotePath) {
  var settingFile = loadJsonSync(conf.sftp.server);
  if (remotePath) {
    settingFile.remotePath += remotePath;
  }
  gulp.task('sftp', function() {
    console.log('ファイルアップします');
    return gulp.src(localPath).pipe(sftp(settingFile));
  });
  // sftpタスクが完了してからreloadを実行する
  gulp.task('reload', ['sftp'], function() {
    console.log('リロードします');
    bs.reload();
  });
  gulp.start(['reload']);
};

/************************************************
 * タスク一覧
 ************************************************/
/* browserSyncのビルド */
gulp.task('server', function() {
  bs.init(conf.browserSync.proxy);
});

/* 監視タスク */
gulp.task('watch', function() {
  gulp.watch(conf.watchPath, function(e) {
    // 変更ファイルのpathを取得
    // c:/Users/...始まりだが動作に影響はないためこのまま
    var localPath  = e.path;

    // アップロード先のpathを取得
    // sftpconfig.jsonのremotePathの後ろに追加する前提
    // そのためベースディレクトリ以下からファイルまでのディレクトリ名を取得（ベースディレクトリを除く）
    // バックスラッシュが入ってきた場合に備えて / に置換
    var remotePath = path.relative(base.distDir, path.dirname(e.path)).replace(/\\/g, '/') + '/';

    uploadAndReload(localPath, remotePath);
  });
});

/* 全アップロード */
gulp.task('sftp:all', function() {
  var settingFile = loadJsonSync(conf.sftp.server);
  gulp.src(conf.sftp.localPath).pipe(sftp(settingFile));
});

/* defaultタスク */
gulp.task('default', ['server'], function() {
  gulp.start(['watch']);
});
```

## 注意事項
- jsonのremotePathとbase.distDirが上手く結合できない場合は調整します
- 複数ファイルの同時変更時はリロードのタイミングがズレるかもしれません
- リモート側に同じディレクトリがないとアップできません（windowsのみ？
- その場合リモート側に同じディレクトリを用意するか`sftp:all`で一旦丸ごとアップします


# 実行してみる
gulpを実行して動作を確認します。
ライブリロードを動作させるには**localhost:port**をブラウザで開く必要があります。
**proxyを指定した192.168.33.40の方ではありません！**（proxyのオプション`open: true`とすれば自動で開きます）

```
$ gulp
# ファイルを修正
```

**[コンソールイメージ]**
![CLI_gulp.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/e2ed1822-317f-2aef-995f-19916c846f52.gif)

**[画面イメージ]**
![sftp-reload.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/e6c7b14f-9fee-fb98-dcfe-317c21a664a6.gif)


SFTPとリロードが上手くいきました！



# 終わりに
ライブリロードは非常にストレスフリーなので、あらゆる環境で導入したいと思っていました。またプロジェクトによって変更すべき箇所は少ないので、非プログラマーの方でも気軽に使えるかもしれません。**なお同名のファイルは上書きしてしまうのでパスの指定にはご注意を！**

何かおかしな所やこうした方が良いんじゃ？ということがあればご指摘ください！