#まえがき
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のインストールについては割愛。
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の使用するポート設定。
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つずつインストールする。エラーがどれで出たかもわかりやすい。
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
cd /home/vagrant/Docker/webpack && docker build -t hibo/node_webpack .
設定ファイルをwebpackフォルダに保存。
entryやoutputのパスは設定ファイルから見たパスとなる。
/* 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 …確認用
確認用のファイルを以下にメモ。
document.write(require("./content.js"));
module.exports = "It works from content.js! test! ";
<!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を試したいのでその設定とか。
{
"extends": "eslint:recommended",
"env": {
"browser": true,
"es6": true,
"node":true
},
"ecmaFeatures": {
"modules": true
},
"rules": {
"semi": 2,
"no-console":0
}
}
起動のコマンドもシェルにしておく。
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が監視を行うようになる。
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フォルダ以下に置かなくてもよかったなぁ。
E
-VagrantFolder … このフォルダで vagrant up や vagrant ssh や ssh rsubなどする。
-Vagrantfile
+.vagrant
-webapp … vagrantで起動したlinuxと共有するフォルダ
+webpack
+public
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以下のファイル)の変更を感知したらブラウザリロードを設定する。
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" ]
cd /home/vagrant/Docker/express && docker build -t hibo/express .
ビルドするときに、socket.ioのインストールでコケることが多かった。
vagrantの再起動でたいてい直ったのでおそらくメモリ不足の問題。
webapp
+public
-gulp
-tasks
-express.js
gulp関連の設定メモ。
サーバーの自動起動とブラウザのリロードを設定している。
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エラーとなった。
#!/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);
}
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を表示するようにしている。
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としたときに表示が変わるかテストできる。
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 とする。
<!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>
起動コマンドをシェルにしておく。
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する。
docker pull mongo
DBのデータ保存用にディレクトリを作る。
home
-vagrant
-DB
-mongodb
+shell
+Docker
mongodbを起動する。
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からつなぐ設定をする。
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;
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の設定を行う。
cd /home/vagrant/Docker/karma && docker build -t hibo/karma .
ビルド用のシェルを作ったら、以下を設定。
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の設定ファイルを記述
// 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
})
}
起動のシェルは以下。
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テスト環境をつくる