Docker PHP開発環境 ブラウザリロード自動化(Gulp + Browsersync)

目的

Docker上でPHP開発を行う際のブラウザ自動リロードをGulp+Browersyncで行う

前提

  • Ubuntu16.04

  • npm, nodeはインストール済み

この記事で追加するパッケージ

  • gulp-minify-css : cssファイルのminify
  • gulp-coffee : coffeeのcompile
  • gulp-sass : sassのcompile
  • gulp-notify : 通知
  • browser-sync : ブラウザ自動リロード
  • gulp-connect-php : phpのビルドインサーバーを起動

Task RunnerとしてGulpのビルドシステムをセットアップ

  • Gulpをインストールし、minify cssタスクを実行してみる

  • 参考:

phpocean.com

1. install node

  • 省略

  • 確認

node -v

2. install npm

  • 省略

  • 確認

npm -v

3. テスト用のディレクトリ(プロジェクトディレクトリ)を作成しておく

  • gulpをインストールする前にプロジェクトディレクトリを用意しておく
  • よくプロジェクトという単語が出てくるが、ただのルートとなるディレクトリのこと(だと思う)
  • まず、以下の構成でスタート
Build
└── index.html
  • ちなみに、↑のためにtreeコマンドをインストール

vitux.com

4. install gulp

  • Gulpはグローバルインストール
sudo npm install --global gulp

5. package.jsonを用意する

  • プロジェクトディレクトリ(Build)直下にpackage.jsonを作成する
  • 今回は手動で作る(仕組みを理解するためにnpm initは使わない)
  • 以下の様に最初は空っぽでよい
{

}
  • この時点での構成
Build
├── index.html
└── package.json

6. プロジェクトディレクトリ(Build)でGulpをセットアップする

cd Build
sudo npm install --save-dev gulp
  • ちなみに

--save-dev is used to save the package for development purpose. Example: unit tests, minification..

--save is used to save the package required for the application to run.

  • 空っぽだったpackage.jsonに依存関係が追記されている
{
  "devDependencies": {
    "gulp": "^4.0.2"
  }
}
  • この時点での構成
❯ tree -L 1 Build
Build
├── index.html
├── node_modules
├── package-lock.json
└── package.json

7. Gulp Taskを作成する

  • ここからがGulpのメイン
  • 本題とは関係ないけど、minify cssタスクを設定、実行してみる
  • まずは、プロジェクトディレクトリ(Build)直下に、gulpfile.jsを作る。

In order to tell Gulp what tasks it should run, we need to create a special file that will contain/list those tasks -the file is named gulpfile.js.

  • この時点での構成
❯ tree -L 1 Build
Build
├── gulpfile.js
├── index.html
├── node_modules
├── package-lock.json
└── package.json
  • gulp-minify-cssをインストールする。
sudo npm install --save-dev gulp-minify-css
  • インストール後、gulpfile.jsに以下のコードを追記。(今回は参考サイトのコードをそのまま流用)
var gulp = require('gulp');

var minifyCss = require('gulp-minify-css');

gulp.task('mincss', function(){

    var fb = gulp.src('main.css');

        fb.pipe(minifyCss());

        fb.pipe(gulp.dest('main'));

        return fb;
});

8. minify cssタスクを実行してみる

  • まず、テスト用のcssファイル(main.css)を作成する。
  • このcssファイルの内容がタスク実行後にminifyされることを確認する。
body{
    margin:0;
    padding:0;

    background-color:teal;
}
  • この時点での構成
❯ tree -L 1 Build
Build
├── gulpfile.js
├── index.html
├── main.css
├── node_modules
├── package-lock.json
└── package.json
  • 準備が整ったところで、プロジェクトディレクトリ(Build)をカレントにしてタスクを実行しみてる。
❯ gulp mincss
zsh: correct 'gulp' to 'ul' [nyae]? n
[19:53:56] Using gulpfile ~/Build/gulpfile.js
[19:53:56] Starting 'mincss'...
[19:53:56] Finished 'mincss' after 40 ms
  • zshが反応してしまった。。。とりあえずnにしたらgulpコマンドは実行された。

  • タスク実行後にmainディレクトリが作成されている。その中にminifyされたcssファイルが生成されていれば成功。

❯ tree -L 1 Build
Build
├── gulpfile.js
├── index.html
├── main
├── main.css
├── node_modules
├── package-lock.json
└── package.json
  • 確かに、minifyされたcssファイルが生成されている。これでひとまずGulpタスクを1つ設定、実行することができた。
❯ cat main.css
body{margin:0;padding:0;background-color:teal}

fileのwatchとタスク完了のnotifyタスクを追加する

  • fileをwatchするタスクを追加する
  • 今回はcssファイルを修正し、保存した時に↑のminify cssタスクが自動実行されるようにする
  • また、sass fileのcompileが完了した際にnotifyを出すようにする
  • 参考:

phpocean.com

1. 必要なパッケージをインストール

  • プロジェクトディレクトリ(Build)で以下パッケージをインストール
  • permissionの原因でgulp-sassはsudoなしで実行。。
sudo npm install --save-dev gulp-notify

npm install --save-dev gulp-sass

2. Watchタスクを追加、、する前に

  • ここで、Gulpの公式ページでgulpコマンドラインが新しくなっていることを知ったので、インストールしたgulpを一度アンインストールして再度インストール。(参考にしている記事が古いため、gulpfile.jsのjsの書き方も古そう。。。)
sudo npm rm --global gulp

sudo npm install --global gulp-cli

gulpjs.com

  • そして、gulpfile.jsもさすがに書き直した。
const { src, dest } = require('gulp');
const minifyCss = require('gulp-minify-css');

exports.default = function() {
    return src('main.css')
        .pipe(minifyCss())
        .pipe(dest('main'))
}
  • タスク名を設定していないのでこの場合のタスクの実行は引数なし
gulp
  • プロジェクトのtaskを確認できる
❯ gulp --tasks
[22:52:17] Tasks for ~/Build/gulpfile.js
[22:52:17] └── default
  • せっかくなので、task名を設定しておく。あと関数に切り出しておく
const { src, dest } = require('gulp');
const minifyCss = require('gulp-minify-css');

function mincss(cb) {
    return src('main.css')
        .pipe(minifyCss())
        .pipe(dest('main'))
}

exports.mincss = mincss;
❯ gulp --tasks
[22:59:31] Tasks for ~/Build/gulpfile.js
[22:59:31] └── mincss
❯ gulp mincss
  • TaskにはPublic tasksPirvate tasksがある。publicはgulpコマンドで指定して独立で実行できるタスク。

gulpjs.com

  • さらにGulpでは複数あるタスクを直列or並列に結合(compose)し巨大なタスク群を構成できる。
  • series()

    To have your tasks execute in order, use the series() method.

  • parallel()

    For tasks to run at maximum concurrency, combine them with the parallel() method.

gulpjs.com

3. watchタスクを追加

  • watchsrcdestと同じくgulpのobject。
  • なので、watchに関しては今までインストールしてきたパッケージは関係ない
  • 参考記事の書き方が古いので同じことを公式のページを参考にES6以降のJS風に書き換える(これが今時なのかよくわからん。。。)
  • 一番シンプルな使い方はwathc(${監視対象ファイル}, ${TASK})
  • listenするeventはdefaultは全て。対象eventを明示的に指定もできる。

     The watch function takes two arguments: the file(s) to watch and a call to action. The second argument can be a closure (anonymous function) or a javascript object.

const { src, dest, series, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');

function mincss(cb) {
    return src('main.css')
        .pipe(minifyCss())
        .pipe(dest('main'))
}

exports.mincss = function() {
    watch('main.css', series(mincss));
    console.log('seen');
}
  • gulp mincssを実行すると、seenが表示される。(バックグラウンド実行ではない)。この状態がfileをwatchしている状態

  • 複数種類のファイルをwatchしてみる

  • 今回はcoffeeスクリプトwatchを追加してみる
  • まず、gulp-coffeeをインストール
npm install --save-dev gulp-coffee
  • gulpfile.jsを編集。
    • coffeeスクリプトコンパイルする関数を追加
    • タスクをdefaultに変更
    • watchに登録するタスクにscriptsを追加。
    • 並列でいいかと思ったのでparallelに変更
  • defaultにしたので実行コマンドは以下でOK
gulp
  • 変更後
const { src, dest, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');

function mincss(cb) {
    return src('*.css')
        .pipe(minifyCss())
        .pipe(dest('main'))
}

function scripts(cb) {
    return src('*.coffee')
        .pipe(coffee())
        .pipe(dest('js'));
}

exports.default = function() {
    watch(['*.css', '*.coffee'], parallel(scripts, mincss));
    console.log('seen');
}
  • 追加したわりに試さないんかーいとなった

4. さらにSassファイルもcompileしてみる

  • さらに本題が外れるがSassのcompileもしてみる
  • まずは、gulp-sassをインストールする
npm install --save-dev gulp-sass
  • main.cssmain.scssにリネーム
  • sassをcompileするタスクを追加する。
    • それ以外にも、watchを切り出したのとseries,parallelをネストして整理してみた
    • parallelとseriseのネストは公式ドキュメントの通りにした。(色々やるとエラーが出た。非同期怖い)
const { src, dest, series, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');
const sass = require('gulp-sass');

function mincss(cb) {
    return src('main.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(minifyCss())
        .pipe(dest('main'))
}

function scripts(cb) {
    return src('script.coffee')
        .pipe(coffee())
        .pipe(dest('js'));
}

function watcher(cb) {
    watch(['*.scss', '*.coffee'], series(mincss, scripts));
    console.log('seen');
}

exports.build = series(mincss, scripts);
exports.default = parallel(series(mincss, scripts), watcher);
  • main.scssの中身を編集する
$color-title: #333;

h1{
    color:$color-title;
}
  • gulpを実行してみる(gulp buildでもいい)
  • mainディレクトリに以下の内容でmain.cssが生成されていれば成功
h1{color:#333}

5. Nofificationが出るようにする

  • まだまだ本題から外れる
  • まずは、gulp-sassをインストールする
npm install --save-dev gulp-notify
  • gulpfile.jsを編集する
const { src, dest, series, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');
const sass = require('gulp-sass');
const notify = require('gulp-notify');

function mincss(cb) {
    return src('main.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(minifyCss())
        .pipe(dest('main'))
        .pipe(notify('Done!'));
}

function scripts(cb) {
    return src('script.coffee')
        .pipe(coffee())
        .pipe(dest('js'));
}

function watcher(cb) {
    watch(['*.scss', '*.coffee'], series(mincss, scripts));
    console.log('seen');
}

exports.build = series(mincss, scripts);
exports.default = parallel(series(mincss, scripts), watcher);
  • gulp or gulp buildを実行して、通知が出ればOK

Browsersyncを用いたLive Reloading [静的ファイル]

  • 少し本題に近づく
  • 参考:

phpocean.com

www.browsersync.io

1. Browsersyncでブラウザ自動リロードしてみる

npm install --save-dev browser-sync
  • gulpfile.jsを編集する
    • この設定はBrowsersyncの公式ドキュメントを参考に書いてみた
    • https://www.browsersync.io/docs/gulp
    • Browsersyncはローカルサーバーを立ち上げる
      • baseDir: たぶんローカルサーバーのドキュメントルート。プロジェクトのディレクトリにしておけばよさそう
      • port: ローカルサーバーのポート。指定しない場合は3000
      • open: task実行時にブラウザの自動起動のon/off
      • notify: 通知するかどうか
    • 今回はindex.htmlだけwatchして、変更があった際に自動リロードするようにした
const { src, dest, series, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');
const sass = require('gulp-sass');
const notify = require('gulp-notify');
const browserSync = require('browser-sync').create();

function mincss(cb) {
    return src('main.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(minifyCss())
        .pipe(dest('main'))
        .pipe(notify('Done!'));
}

function scripts(cb) {
    return src('script.coffee')
        .pipe(coffee())
        .pipe(dest('js'));    
}

function html(cb) {
    src('index.html');
}

function browserSyncInit(cb) {
    browserSync.init({
        server : {
            baseDir: './' 
        },
        port: 8081,
        open: true,
        notify: true
    });
}

function watcher(cb) {
    browserSyncInit();
    // compile tasks
    watch(['*.scss', '*.coffee', 'index.html'], series(mincss, scripts, html));
    // live reload task
    watch('index.html').on('change', browserSync.reload);

    console.log('under watching...');
}

exports.build = series(mincss, scripts);
exports.default = parallel(series(mincss, scripts), watcher);
  • gulp実行を実行すると自動でブラウザ(or tab)も起動し、index.htmlの内容が表示される
gulp
  • 試しに、index.htmlの内容を変更するとブラウザが自動でリロードされることが確認できればOK

エラー ENOSPC: System limit for number of file watchers reached

1.発生したエラー

  • 次の作業をするために試行錯誤しているうちにこのエラーが発生するようになった。
❯ gulp
[22:46:02] Using gulpfile ~/Build/gulpfile.js
[22:46:02] Starting 'default'...
[22:46:02] Starting 'watcher'...
[22:46:02] Starting 'mincss'...
[22:46:02] 'watcher' errored after 56 ms
[22:46:02] Error: ENOSPC: System limit for number of file watchers reached, watch 'index.html'
    at FSWatcher.start (internal/fs/watchers.js:165:26)
    at Object.watch (fs.js:1275:11)
    at createFsWatchInstance (/home/shouhei/Build/node_modules/chokidar/lib/nodefs-handler.js:38:15)
    at setFsWatchListener (/home/shouhei/Build/node_modules/chokidar/lib/nodefs-handler.js:81:15)
    at FSWatcher.NodeFsHandler._watchWithNodeFs (/home/shouhei/Build/node_modules/chokidar/lib/nodefs-handler.js:233:14)
    at FSWatcher.NodeFsHandler._handleFile (/home/shouhei/Build/node_modules/chokidar/lib/nodefs-handler.js:262:21)
    at FSWatcher.<anonymous> (/home/shouhei/Build/node_modules/chokidar/lib/nodefs-handler.js:495:21)
    at FSReqCallback.oncomplete (fs.js:160:5)
[22:46:02] 'default' errored after 61 ms
[22:46:02] The following tasks did not complete: <series>, mincss
[22:46:02] Did you forget to signal async completion?

2.原因

  • 原因はgulpではなくOS(Ubuntu)のinotifyの監視ファイル数のlimitを超えたことによるエラーだった。

参考:

github.com

3.対処

  • 参考:

github.com

  • まず、現在のinotifyの上限を確認
❯ cat /proc/sys/fs/inotify/max_user_watches
8192
  • 今回は一時的に上限を変更
❯ sudo sysctl fs.inotify.max_user_watches=524288
fs.inotify.max_user_watches = 524288

~/Build
❯ cat /proc/sys/fs/inotify/max_user_watches
524288
  • sysctl関連のファイルのリロード(ipv6の設定ファイルがないらしいが今回の件とは関係ないので放置)
❯ sudo sysctl -p
sysctl: cannot stat /proc/sys/net/ipv6/conf/all/disable_ipv6: そのようなファイルやディレクトリはありません
sysctl: cannot stat /proc/sys/net/ipv6/conf/default/disable_ipv6: そのようなファイルやディレクトリはありません
  • 上限を変更後、gulpを実行するとエラーは発生しなくなった

Browsersyncをを用いたLive Reloading [動的ファイル]

1. index.phpを用意

  • phpファイルをwatchして、ブラウザ自動リロードする
  • index.htmlindex.php に変更

2. gulpfile.jsも修正

  • gulpfile.jsもindex.phpに変更する
const { src, dest, series, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');
const sass = require('gulp-sass');
const notify = require('gulp-notify');
const browserSync = require('browser-sync').create();

const paths = {
    html: 'index.php',
    css: 'main.scss',
    script: 'script.coffee'

}

function mincss(cb) {
    return src(paths.css)
        .pipe(sass().on('error', sass.logError))
        .pipe(minifyCss())
        .pipe(dest('main'))
        .pipe(notify('Done!'));
}

function scripts(cb) {
    return src(paths.script)
        .pipe(coffee())
        .pipe(dest('js'));    
}

function html(cb) {
    src(paths.html);
}

function browserSyncInit(cb) {
    browserSync.init({
        server : {
            baseDir: './' 
        },
        port: 8081,
        open: true,
        notify: true
    });
}

function watcher(cb) {
    browserSyncInit();
    // compile tasks
    watch(['*.scss', '*.coffee', paths.html], series(mincss, scripts, html));
    // live reload task
    watch(paths.html).on('change', browserSync.reload);

    console.log('under watching...');
}

exports.build = series(mincss, scripts);
exports.default = parallel(series(mincss, scripts), watcher);

3. gulp実行するが、Errorとなる

  • gulpを実行。しかし、ローカルサーバーが起動し、ブラウザも自動起動するがCannot GET / が表示されるだけ。(タブにはErrorと表示されている。)
  • 原因は、gulpのローカルサーバーではphpまでは実行してくれない。何かしらphpサーバーを用意する必要がある

4. gulp-connect-php

  • 今回は、phpのビルドインサーバーを起動するパッケージ gulp-connect-php を使ってみる

www.npmjs.com

5. 必要なパッケージをインストール

npm install --save-dev gulp-connect-php

6. gulpfile.jsを修正

  • 前回のstatic file serverの場合: Browsersyncがローカルサーバーとなっていた
  • 今回はgulp-connect-phpでビルドインのサーバーを立ち上げ、BrowsersyncはそのサーバーのProxyとなる
    • クライアント(ブラウザ)からProxy(Browsersync localhost:3000)を介して、phpビルドインサーバー(localhost:8082)に接続する
const { src, dest, series, parallel, watch } = require('gulp');
const minifyCss = require('gulp-minify-css');
const coffee = require('gulp-coffee');
const sass = require('gulp-sass');
const notify = require('gulp-notify');
const browserSync = require('browser-sync').create();
const connectPHP = require('gulp-connect-php')

const paths = {
    html: 'index.php',
    css: 'main.scss',
    script: 'script.coffee'
}

function mincss(cb) {
    return src(paths.css)
        .pipe(sass().on('error', sass.logError))
        .pipe(minifyCss())
        .pipe(dest('main'))
        .pipe(notify('Done!'));
}

function scripts(cb) {
    return src(paths.script)
        .pipe(coffee())
        .pipe(dest('js'));
}

function html(cb) {
    src(paths.html);
}

function browserSyncInit(cb) {

    //-------------------------------------
    // Start a Browsersync static file server
    //-------------------------------------
    // browserSync.init({
    //     server : {
    //         baseDir: './' 
    //     },
    //     port: 8081,
    //     open: true,
    //     notify: true
    // });

    //-------------------------------------
    // Start a Browsersync proxy
    //-------------------------------------
    browserSync.init({
        proxy: 'http://localhost:8082'
    });
}

function watcher(cb) {
    browserSyncInit();
    // compile tasks
    watch(['*.scss', '*.coffee', paths.html], series(mincss, scripts, html));
    // live reload task
    watch(paths.html).on('change', browserSync.reload);

    console.log('under watching...');
}

function php(cb) {
    connectPHP.server({
        base: '.',
        hostname: 'localhost',
        port:8082
    })
}

exports.build = series(php, mincss, scripts);
exports.default = parallel(series(php, mincss, scripts), watcher);

7. gulpを実行

  • ブラウザが自動起動するが今回はBrowsersyncのプロキシを介すので、localhost:3000にアクセスすることになる
    • なので、裏で起動してるphpサーバー(localhost:8082)には直接アクセスしない

f:id:mizushou:20190609231727p:plain

❯ gulp
[01:58:06] Using gulpfile ~/Build/gulpfile.js
[01:58:06] Starting 'default'...
[01:58:06] Starting 'watcher'...
[01:58:06] Starting 'mincss'...
under watching...
PHP 7.0.33-0ubuntu0.16.04.4 Development Server started at Sun Jun  9 01:58:07 2019
Listening on http://localhost:8082
Document root is /home/shouhei/Build
Press Ctrl-C to quit.
[Browsersync] Proxying: http://localhost:8082
[Browsersync] Access URLs:
 -------------------------------------
       Local: http://localhost:3000
    External: http://192.168.1.23:3000
 -------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 -------------------------------------
[Sun Jun  9 01:58:07 2019] 127.0.0.1:56466 [200]: /
[01:58:07] gulp-notify: [Gulp notification] Done!
[01:58:07] Finished 'mincss' after 250 ms
[01:58:07] Starting 'scripts'...
[01:58:07] Finished 'scripts' after 102 ms
[Sun Jun  9 01:58:07 2019] 127.0.0.1:56474 [200]: /
[01:58:17] Starting 'mincss'...
[01:58:17] gulp-notify: [Gulp notification] Done!
[01:58:17] Finished 'mincss' after 28 ms
[01:58:17] Starting 'scripts'...
[01:58:17] Finished 'scripts' after 12 ms
[01:58:17] Starting 'html'...
[Browsersync] Reloading Browsers... (buffered 2 events)
[Sun Jun  9 01:58:17 2019] 127.0.0.1:56532 [200]: /

8. 最後にブラウザ自動リロードが実行されるかを確認する

  • index.phpを編集し、ブラウザ自動リロードが実行されればOK