LoginSignup
11
9

More than 5 years have passed since last update.

WindowsのVagrant-ubuntu上でDockerを使ってjavascriptの開発環境を作ってみたメモ

Last updated at Posted at 2015-12-23

まえがき

javascriptでブラウザ上で動く何かを作りたいんだけど、
最近は周辺ツールがいろいろ出ている。
使ったら便利だろうなーと思いつつ手が出せていなかったので、今回いろいろ試してみた。
試して見たのは以下の通り。

  • Vagrant
  • Docker
  • webpack
  • babel
  • gulp
  • express
  • socket.io
  • mongodb
  • karma

開発環境

環境は以下のとおり。

  • Windows7
  • git 1.9.4
  • vagrant1.7.4
  • virtualbox5.0.10
  • 仮想マシンubuntu14.04
  • 仮想マシン上Docker 1.6.2

Vagrantの設定

vagrantでubuntu14.04を使用。
dockerのインストールまで記述。

普段、私はテキストエディタにsublimeを使用。
vagrant上のファイルをsublimeで編集したいので、rsubの設定もする。
sublimeへのrsubのインストールについては割愛。

Vagrantfile.
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu14.04"
  config.vm.network "private_network", ip: "192.168.50.10"
  config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"

# windows上のフォルダ(Vagrantfileと同じ階層のwebappフォルダ)とvagrant上のディレクトリの同期設定
  config.vm.synced_folder "./webapp", "/home/vagrant/Docker/webapp"

  config.vm.provision "shell", inline: <<-SHELL
# dockerを入れる設定
    sudo apt-get update
    sudo apt-get install -y docker.io
    sudo chown vagrant:vagrant /home/vagrant/Docker

# sudo しなくてもdockerコマンドが使えるようにする
    sudo gpasswd -a vagrant docker

# rsubを入れる設定
    sudo wget -O /usr/local/bin/rsub https://raw.github.com/aurora/rmate/master /rmate
    sudo chmod +x /usr/local/bin/rsub
  SHELL
end

rsub用のssh設定。RemoteForward 52698 127.0.0.1:52698がrsubの使用するポート設定。

ssh/config.
Host rsub
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile E:/VagrantFolder/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL
  RemoteForward 52698 127.0.0.1:52698

gitをwindowsにインストールしておく。gitbashで以下作業を行う。
以下のコマンドを入力。

cd E:/VagrantFolder
vagrant box add ubuntu14.04 https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box

インストールできたら、vagrant upで起動。 ssh rsubで接続。
切るときは vagrant halt
dockerのbulidに失敗したら再起動すると直ることが多かったので、
この3つは覚えておくとよい。

gitbashのショートカットを作って、ショートカットのプロパティを開いて、
作業フォルダにE:/VagrantFolderと設定しておくと以降楽になる。

webpackのインストール

package.jsonでのインストールはメモリ不足になる可能性があるので、
1つずつインストールする。エラーがどれで出たかもわかりやすい。

Docker/webpack/Dockerfile.
FROM node:5.3.0

WORKDIR /usr/local/src
RUN npm install -g webpack
RUN npm i babel-core babel-preset-es2015 babel-preset-react
RUN npm i eslint
RUN npm i pre-commit precommit-hook
RUN npm i lodash
RUN npm i react

RUN npm i eslint-loader
RUN npm i babel-loader
RUN npm i css-loader
RUN npm i react-hot-loader
RUN npm i style-loader
CMD [ "node" ]

ビルドは何回かやることになるので、シェルを作っておく。
シェルファイルは作ったら権限を与えるのを忘れずに。chmod 755 shell/build_webpack.sh

shell/build_webpack.sh
cd /home/vagrant/Docker/webpack && docker build -t hibo/node_webpack .

設定ファイルをwebpackフォルダに保存。
entryやoutputのパスは設定ファイルから見たパスとなる。

webapp/webpack/webpack.config.js
/* global module, __dirname */
module.exports = {
    entry: __dirname +"/../public/app/scripts/entry.js",
    output: {
        path: __dirname + "/../public/app/dist",
        filename: "bundle.js"
    },
    module: {
        loaders: [
            { test: /\.css$/,  loader: "style!css" },
            { test: /\.md$/,   loader: "html!markdown?gfm=false" },
            { test: /\.jsx?$/, loader: 'babel?presets[]=react,presets[]=es2015', exclude: /node_modules/},
            { test: /\.js?$/,  loader: 'babel?presets[]=react,presets[]=es2015', exclude: /node_modules/},
            { test: /\.js$/,   loader: "eslint-loader",        exclude: /node_modules/},
            { test: /\.less$/, loader: "style!css!less" }
        ]
    },
    watchOptions: {
     poll: true
    }
};

ここで、以下のようにファイルを置いている。

webapp
  -webpack
    -webpack.config.js … webpackの設定ファイル
  -public
    -app
      -scripts … ここのフォルダにjsファイルをまとめておく
        -.eslintrc …eslintの設定ファイル
        -entry.js … webpackの監視するentryファイル
        -content.js … 確認用
      -dist … ここのフォルダにwebpackがoutputする
        -bundle.js …webpackを起動すると生成されるbundleファイル。
      -views
        -index.html …確認用

確認用のファイルを以下にメモ。

webapp/public/app/scritps/entry.js
document.write(require("./content.js"));
webapp/public/app/scripts/content.js
module.exports = "It works from content.js! test! ";
webapp/public/app/views/index.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>webpack tutorial</title>
        <script src="../dist/bundle.js"></script>
    </head>
    <body>
    </body>
</html>

ちなみに、eslintファイルは以下のように設定。
es6を試したいのでその設定とか。

webapp/public/app/scripts/.eslintrc
{
    "extends": "eslint:recommended",
    "env": {
        "browser": true,
        "es6": true,
        "node":true
    },
    "ecmaFeatures": {
        "modules": true
    },
    "rules": {
        "semi": 2,
        "no-console":0
    }
}

起動のコマンドもシェルにしておく。

shell/webpack_docker_start.sh
docker run --rm -it --name webpack -p 8080:8080 -v /home/vagrant/Docker/webapp:/usr/local/src/app hibo/node_webpack /bin/bash -c 'cd /usr/local/src; webpack --config ./app/webpack/webpack.config.js --progress --colors --watch;'

これで、shell/webpack_docker_start.shを起動すると、webappフォルダがdocker上のappフォルダと共有されて、そこでwebpackが監視を行うようになる。

Docker上のフォルダ構成.
usr
  -local
    -src … このフォルダでwebpackコマンドを実行する。
      +node_modules … 最初にnpmでインストールされたパッケージ郡
      -app …このフォルダとwebappを共有
        +webpack …webapp以下のwebpack
        +public  …webapp以下のpublic

node_moduleのある階層から1つ下のフォルダを共有しているのは、
node_moduleを共有したくないから。
ubuntuで動かす分には共有しても問題ないけど、今回はwindowsと共有している。
windowsではファイル名+パスの限界が260文字くらいに設定されている。
node_module以下は階層が深くなりやすいので、これを共有しようとするとエラーになる。

自分のフォルダ構成は以下の感じにしてみている。今思うと、だいぶ汚い。
vagrant上のwebappフォルダはDockerフォルダ以下に置かなくてもよかったなぁ。

Windows上のフォルダ構成.
E
  -VagrantFolder … このフォルダで vagrant up や vagrant ssh や ssh rsubなどする。
    -Vagrantfile
    +.vagrant
    -webapp … vagrantで起動したlinuxと共有するフォルダ
      +webpack
      +public
Vagrant+ubuntu1.04のフォルダ構成.
home
  -vagrant … vagrant ssh や ssh rsub を実行したときに接続されるディレクトリ
    -shell
      -build_webpack.sh
      -webpack_docker_start.sh
   -Docker
     -webapp … windows上のwebappフォルダと共有
       + webpack
       + public
     -webpack
       - Dockerfile

gulp + express + socket.io

次はexpressの設定をしたい。
ファイルを編集したらexpressサーバー再起動、ブラウザのリロードの自動化を行う。
そのために、タスクランナーのgulpを導入。
gulpでサーバー側用のjsファイルの変更を感知したら再起動。
クライアント側用のファイル(public以下のファイル)の変更を感知したらブラウザリロードを設定する。

Docker/express/Dockerfile.
FROM node:5.3.0

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

#gulpfileを書き出し
RUN echo "var requireDir = require('require-dir'); requireDir('webapp/gulp/tasks', { recurse: true }); " > gulpfile.js 

#package.jsonを書き出し
RUN echo '{ "name": "node_express",  "scripts": { "start": "node webapp/bin/www" }}' > package.json

#タスクランナーインストール
RUN npm install -g gulp
RUN npm install gulp 
RUN npm install --save-dev require-dir 
RUN npm install --save-dev gulp-if 
RUN npm install --save-dev gulp-livereload

#サーバーインストール
RUN npm install --save-dev express 
RUN npm install --save-dev body-parser 
RUN npm install --save-dev cookie-parser 
RUN npm install --save-dev debug 
RUN npm install --save-dev jade 
RUN npm install --save-dev morgan 
RUN npm install --save-dev serve-favicon 

# ウェブソケットインストール
RUN npm install --save-dev socket.io

# DBドライバインストール
RUN npm install --save-dev mongodb

CMD [ "gulp", "watch" ]
shell/build_experss.sh
cd /home/vagrant/Docker/express && docker build -t hibo/express . 

ビルドするときに、socket.ioのインストールでコケることが多かった。
vagrantの再起動でたいてい直ったのでおそらくメモリ不足の問題。

webapp
  +public
  -gulp
    -tasks
      -express.js

gulp関連の設定メモ。
サーバーの自動起動とブラウザのリロードを設定している。

webapp/gulp/tasks/express.js
var gulp = require('gulp');
var spawn = require('child_process').spawn;
var server;

var livereload = require('gulp-livereload');

// ブラウザリロードタスク。
gulp.task('reload',function(){
     gulp.src(['webapp/public/*/*','webapp/views/*.jade'])
     .pipe(livereload());  
});

gulp.task('server',function(){
    if(server){
         //サーバーを終了
         server.kill('SIGKILL');
    }
     //サーバーを起動
     server = spawn('node',['./webapp/bin/www']);

     // サーバーの出力をキャッチして出力。
     server.stdout.on('data', function (data) {
        console.log('stdout: ' + data);
     });
     server.stderr.on('data', function (data) {
        console.log('stderr: ' + data);
     });
});

gulp.task('watch',['server'],function(){
    // webapp配下のpublicフォルダを除くjsファイルが更新されたらサーバーを再起動
    gulp.watch(['webapp/**/*.js','!webapp/public/**/*.js'],['server']);

    // publicフォルダ以下のファイルを更新したらブラウザリロード
    livereload.listen();
    gulp.watch(['webapp/public/**/**', 'webapp/views/*.jade'], ['reload']);
});

express関連のファイルの設定メモ。
express-generatorで作ったファイルを参考にしている。
socket.ioのサイトのチュートリアルではapp.js内にsocket.ioの設定を書いているが、
express4ではbin/wwwファイルにそれを書かないと404エラーとなった。

webapp/bin/www.
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3333');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

var io = require('socket.io').listen(server);

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});


/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

webapp/app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');


// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

module.exports = app;

ここで、expressに合わせてファイルを配置する。

 webapp
  +public … webpackで監視しているクライアント側ファイル
  +gulp … gulp用の設定ファイル
  -bin
    -www …expressの起動ファイル
  -routes
    -index.js
    -users.js
  -views
    -error.jade
    -layout.jade
    -index.jade

ルートの設定。接続したらindex.htmlを表示するようにしている。

webapp/routes/index.js
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  //res.render('index', { title: 'Express' });
  res.sendFile(__dirname + '/../public/app/views/index.html');
});

router.get('/test', function(req, res, next) {
  res.render('index', { title: 'Express' });
});


module.exports = router;

users.jsで/userとしたときに表示が変わるかテストできる。

webapp/routes/users.js
var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

module.exports = router;

更新するファイル側からもlivereloadを呼び出ように変更。
参考にしたサイトではlocalhost:35729/livereload.jsだが、
vagrantの仮想環境上にdockerを立ち上げて接続などとしているので
http: //192.168.50.10:35729 とする。

webapp/public/app/views/index.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>webpack tutorial</title>
        <script src="../dist/bundle.js"></script>
        <script src="http://192.168.50.10:35729/livereload.js"></script>
    </head>
    <body>
    </body>
</html>

起動コマンドをシェルにしておく。

shell/express_docker_start.sh
docker run --name express --rm -it -p 80:3333 -p 35729:35729 -v /home/vagrant/Docker/webapp:/usr/src/app/webapp hibo/express

mongodb

DBの設定をしてみる。
imageをpullする。

shell/build_mongodb.sh
docker pull mongo

DBのデータ保存用にディレクトリを作る。

home
  -vagrant
   -DB
     -mongodb
   +shell
   +Docker

mongodbを起動する。

shell/mongodb_docker_start.sh
docker run --name mongodb -p 27017:27017 -v /home/vagrant/DB/mongodb:/data/db -d mongo
docker run -it --link mongodb:mongo --rm mongo sh -c 'exec mongo "$MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT/test"'

expressからつなぐ設定をする。

webapp/routes/mongodb.js
var mongodb = require('mongodb'),
    mongoServer = new mongodb.Server(
      '192.168.50.10',
      27017),
    dbHandle = new mongodb.Db('spa',mongoServer, {safe:true});

dbHandle.open(function(){
  console.log('**connected to mongoDB');
});

module.exports = dbHandle;
webapp/app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');
var dbHandle = require('./routes/mongodb');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');


// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

module.exports = app;

Karmaでテスト

テスト用にKarmaの設定を行う。

shell/build_karma.sh
cd /home/vagrant/Docker/karma && docker build -t hibo/karma .

ビルド用のシェルを作ったら、以下を設定。

Docker/karma/Dockerfile.
FROM node:4.2.3

RUN mkdir -p /usr/src/app/spec
WORKDIR /usr/src/app

RUN npm install -g --unsafe-perm phantomjs
RUN npm install -g --unsafe-perm jasmine-core
RUN npm install -g --unsafe-perm karma-jasmine
RUN npm install -g --unsafe-perm karma-phantomjs-launcher
RUN npm install -g --unsafe-perm karma

CMD [ "node" ]

karma内でsocket.ioを使っているのでこれもビルド時にメモリ不足になりやすい。

webapp
  -karma
    -karma.conf.js
  +public
  +gulp
  +bin
  +routes

karmaの設定ファイルを記述

webapp/karma/karma.conf.js
// Karma configuration
// Generated on Mon Dec 21 2015 10:38:17 GMT+0000 (UTC)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser
    files: [
      '../public/app/dist/*.js',
      '../public/test/spec/**Spec.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['PhantomJS'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultanous
    concurrency: Infinity
  })
}

起動のシェルは以下。

shell/karma_docker_start.sh
docker run -it --name karma --rm -v /home/vagrant/Docker/webapp:/usr/src/app hibo/karma  /bin/bash -c 'karma start ./karma/karma.conf.js'

これでテストが可能になる。

以上、いろいろテストしながら環境を作ったけど、vagrantを噛ませる必要はあんまりないかも。

参考にしたページ

参考:DockerfileのONBUILD
参考:Docker Registryを使う
参考:vagrantでinaccessibleが出た時
参考:Windows 7マシンで、VirtualBox+Vagrantを使いUbuntu 14.04を動かす
参考:Vagrantでアプリケーション開発環境をローカルPCに作ってみよう
参考:Vagrant、chef、 Dockerって最近よく聞くけどなにが違うの?
参考:Express
参考:Docker チートシート
参考:Dockerコマンドメモ

webpack-dev-serverで継続的なクライアントサイドテスト
webpackとwebpack-dev-server、html-loaderと合わせて、JsRenderを試す(再)
webpack dev server

参考:node初心者がexpress-generatorで吐き出されたapp.jsを読んでみる
参考:Express application generator
web-socketのチュートリアル
【Node.js】express4 + socket.io で socket.io.js が 404 not found になる。Add Star

参考:webpack+babel-loader+power-assert+jsdomでフロントエンド開発環境を作る
React+FluxでTodoMVCを作ってFluxについて学んでみた

2015年はgulpで決まり!開発環境をgruntから乗り換えよう!(コーダー編)
gruntを知らない人がgulpを使ってみた
gulp.jsを使ってフロントエンドのビルドをする【webpack, stylus】
Getting Started
Gulp.js入門 – コーディングを10倍速くする環境を作る方法まとめ
これからはじめるGulp(1):bundler, rbenv, nodebrewでGulp環境を作ってみる

webpack-dev-serverで継続的なクライアントサイドテスト
webpackとwebpack-dev-server、html-loaderと合わせて、JsRenderを試す(再)
webpack dev server

Node.js + express4 + gulp 簡単にブラウザリロードを実装
gulp - ブラウザのリロード自動化
2014/11/27 ブラウザ確認が一瞬! Grunt・Gulpと始めるBrowserSync入門
【Gulp.jsインストール】フロントエンドタスクランナーGulp.jsの使い方
【エラー】Refusing to install as a dependency of itself
gulp-livereload
参考:dockerで複数個のポートフォワーディングを行う

MongoDBをDockerでインストールする
Official Repository mongo

Jenkinsの公式Dockerイメージ使ってみた
コードの健康状態を保て! Jenkinsとtestemを使って、JavaScriptで継続的インテグレーション(CI)を行う
PonteのインストールとMQTT over SSL/TLS設定
[Node.js] npmの設定 属性一覧
Karma, PhantomJS, JasmineでBDDなJavaScriptテスト環境をつくる

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9