LoginSignup
18
17

More than 5 years have passed since last update.

MithrilとES6のチュートリアル - Part 1: GulpとWebpackのミニマルなビルド環境

Last updated at Posted at 2015-12-03

 ここ数年のSPAはExtJSを使っているのですがES6時代は新しいフレームワークに変えようと思っていろいろと試しています。Ember.jsReact.jsで書いていても何か違う感じがしていたのですが、黒ムツ本ことMithrilを読んで気に入ったので、しばらくはMithrilで書いていこうと思います。特にReact.jsの慌ただしさに疲れた人には向いていると思います。

 フロントエンドはトランスパイルするのでimport句を使い、サーバーサイドは最新のNode.js(執筆時点はv5.1.0)でサポートされていないので使っていません。SPAだとサーバーサイドは軽量なREST APIサーバーやWebhookが多くなるので、処理単位の小さい関数で十分なくらいの実装にしたいです。

プロジェクト

 Mithrilはまだ新しいフレームワークなのでビルド環境や他のフレームワークとの連携もサンプルが少なく試行錯誤しているところです。今回は開発環境を整理してみようと思います。リポジトリはこちらです。

$ tree .
.
├── Dockerfile
├── app
│   ├── client
│   │   ├── css
│   │   │   └── main.css
│   │   └── js
│   │       ├── home.js
│   │       ├── main.js
│   │       └── navbar.js
│   └── server
│       └── server.js
├── config.js
├── dist
│   ├── images
│   │   └── favicon.ico
│   └── index.html
├── docker-compose.yml
├── gulpfile.js
├── node_modules -> /dist/node_modules
├── package.json
├── webpack.config.js
└── webpack.config.production.js

Docker

 ベースイメージは公式のNode.jsです。ONBUILD版でも良いのですが、クラウドの仮想マシンにあるDockerホストからEmacsで開発ができるようにしています。

Dockerfile
FROM node:5.1
MAINTAINER Masato Shimizu <ma6ato@gmail.com>

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN mkdir -p /dist/node_modules &&\
    ln -s /dist/node_modules /usr/src/app/node_modules
COPY package.json /usr/src/app/
RUN npm install
COPY . /usr/src/app/
CMD ["npm", "start"]

 docker-compose.ymlでプロジェクトのルートディレクトリをコンテナにマウントします。node_modulesはシムリンクにして隠れないようにしています。Dockerを使って開発するのは好みなのでMithrilをES6で書くことにあまり関係はありませんが。

docker-compose.yml
mithril:
  restart: always
  build: .
  volumes:
    - .:/usr/src/app
  ports:
    - "3030:3000"

ビルド環境

 ビルド環境はGulpWebpackのを合わせて使います。

pakage.json

 package.jsonのscriptsフィールドはgulpコマンド経由で実行します。Dockerfileに書いたCMD命令から呼ばれるstartでプロダクションのビルドとExpressが起動するようにしています。

package.json
    "scripts": {
        "start":   "gulp webpack && NODE_ENV=production node ./app/server/server",
        "nodemon": "gulp nodemon",
        "watch":   "gulp watch",
        "build":   "gulp webpack"
    }

開発環境のタスクはnodemonが監視とリロードの面倒を見てくれます。

gulp.js
gulp.task('nodemon', () => {
  nodemon({
      script: './app/server/server.js',
      ext: 'js',
      watch: ['./app/server'],
      env: { 'NODE_ENV': 'development' }
  })
});

 プロダクション環境はwebpackタスクで別に分けているwebpack.config.production.jsを実行してから通常のExpresサーバーを起動しています。

gulp.js
gulp.task('webpack', (callback) => {
    let myConfig = Object.create(webpackConfig);
    webpack(myConfig, function(err, stats) {
        if(err) throw new gutil.PluginError('webpack', err);
        gutil.log('[webpack]', stats.toString({
            colors: true
        }));
        callback();
    });
});

webpack.config.js

 開発中のコード編集によるリロードはサーバーサイドはnodemonを使い、フロントエンドはwebpack-hot-middlewarewebpack-dev-middlewareが監視します。

server.js
const isProduction = process.env.NODE_ENV === 'production';

if(!isProduction) {
    const webpack = require('webpack');
    const webpackConfig = require('../../webpack.config.js');
    const compiler = webpack(webpackConfig);

    app.use(require('webpack-dev-middleware')(compiler, {
        noInfo: true, publicPath: webpackConfig.output.publicPath
    }));
    app.use(require('webpack-hot-middleware')(compiler));
}

 webpack-config.jsは本当は開発用とプロダクション用で1つのファイルにしたいのですが良い方法が見つからないので現状は分けています。

webpack-config.js
'use strict';
const webpack = require('webpack');
const path = require('path');
module.exports = {
    devtool: 'eval-source-map',
    entry: [
        'webpack-hot-middleware/client',
        path.join(__dirname, 'app/client/js/main.js')
    ],
    output: {
        path: path.join(__dirname, 'dist/js'),
        filename: '[name].js',
        publicPath: '/js/'
    },
    plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ],

 プロダクション環境のpluginsフィールドではDedupePluginとUglifyJsPluginを使いコードの最適化とsourceMapを作成しないように変えています。

webpack-config.production.js
'use strict';
const webpack = require('webpack');
const path = require('path');
module.exports = {
    entry: [
        path.join(__dirname, 'app/client/js/main.js')
    ],
    output: {
        path: path.join(__dirname, 'dist/js'),
        filename: '[name].js',
        publicPath: '/js/'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
            }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({ sourceMap: false } )
    ],

サーバーの起動

 開発サーバーはフラグをつけたdocker-compose runコマンドで使い捨てのコンテナを起動します。サーバーとクライアントのどちらのコードを編集してもビルドとリロードをしてくれるので、IDEを使わなくてもEmacsでテンポ良く開発ができます。クラウドの仮想マシンとDockerの組み合わせなので、開発する場所も(インターネットとターミナルがあれば)デプロイする場所からも解放されて自由にプログラミングができます。

$ docker-compose run \
  --rm --service-ports \
  mithril \
  npm run nodemon
npm info it worked if it ends with ok
npm info using npm@3.3.12
npm info using node@v5.1.0
npm info lifecycle docker-mithril-study@0.0.1~prenodemon: docker-mithril-study@0.0.1
npm info lifecycle docker-mithril-study@0.0.1~nodemon: docker-mithril-study@0.0.1

> docker-mithril-study@0.0.1 nodemon /usr/src/app
> gulp nodemon

[15:31:30] Using gulpfile /usr/src/app/gulpfile.js
[15:31:30] Starting 'nodemon'...
[15:31:30] Finished 'nodemon' after 2.27 ms
[15:31:30] [nodemon] 1.8.1
[15:31:30] [nodemon] to restart at any time, enter `rs`
[15:31:30] [nodemon] watching: /usr/src/app/app/server/**/*
[15:31:30] [nodemon] starting `node ./app/server/server.js`
Example app listening at http://:::3000
webpack built 75d57784c725d9d93cbb in 4393ms

 プロダクションはDocker Composeのupコマンドをデタッチモードで実行します。

$ docker-compose up -d

restart: alwaysフィールドを書けばサービスが自動再起動するのでシンプルなデモナイズはこれだけで済みます。

docker-compose.yml
mithril:
  restart: always
  build: .
  volumes:
    - .:/usr/src/app
  ports:
    - "3030:3000"
18
17
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
18
17