LoginSignup
9
11

More than 5 years have passed since last update.

MithrilとES6のチュートリアル - Part 2: BootstrapをnpmとWebpackでインストールする

Last updated at Posted at 2015-12-05

 黒ムツ本の第5章「Bootstrapを適用する」ではCDNやBowerからBootstrapをインストールする方法が書かれていますが、今回はフロントエンドのモジュール管理にWebpackを使ってみます。こちらの投稿にあるようにWebpackはnpm の利用を推奨しています。サーバーサイドもNode.jsで書く場合は、フロントエンドも同じにするとnpmだけで両方のパッケージ管理ができます。

package.json

 リポジトリはこちらです。Bootstrap用にnpmでインストールしたパッケージです。

package.json
    "devDependencies": {
...
        "jquery": "~2.1.4",
        "mithril": "~0.2.0",
        "bootstrap": "~3.3.5",
...
        "style-loader": "~0.13.0",
        "css-loader": "~0.21.0",
        "imports-loader": "~0.6.5",
        "file-loader": "~0.8.4",
        "url-loader": "~0.5.6",
...

loaders

 Webpackのloadersタスクを使いURLを検証してファイルをロードするときに必要な前処理をインジェクトします。

babel-loader

 前回のようにES6でコードを書くためnpmでインストールしたパッケージ以外の.jsファイルはbabel-loaderを通してロードします。

webpack.config.js
..
            test: /\.js?$/,
            exclude: /node_modules/,
            loader: 'babel'
...

import-loader

 Bootstrapの中にはjQuery変数に依存しているコードがあります。WebpackからjQueryをロードする方法はいくつもあり、書き方もいろいろで悩みます。今回はimport-loaderを使いました。bootstrapモジュールのjQuery変数にjqueryモジュールをインジェクトします。

webpack.config.js
...
        loaders: [{
...
        }, {
            test: /bootstrap\/js\//,
            loader: 'imports?jQuery=jquery'
        }, {
...

style-loader, css-loader, less-loader

 CSSはstyle-loaderからcss-loader経由でロードします。LESSをロードする場合はless-loaderを経由すると自動的に書式を変換してくれます。

webpack.config.js
...
        loaders: [{
...
        }, {
            test   : /\.css$/,
            loader : 'style!css'
        }, {
            test   : /\.less$/,
            loader : 'style!css!less'
        }, {
...

url-loader

 Bootstrapで使うフォントはURLをtestの条件にして、100kbより小さいファイルをurl-loaderからロードします。

webpack.cofig.js
...
        }, {
            test: /\.(png|woff|woff2|eot|ttf|svg)$/,
            loader: 'url?limit=100000'
        }],
...

まとめ

まとめるとmoduleloadersフィールドは以下のようになります。

webpack.config.js
...
        loaders: [{
            test: /\.js?$/,
            exclude: /node_modules/,
            loader: 'babel'
        }, {
            test: /bootstrap\/js\//,
            loader: 'imports?jQuery=jquery'
        }, {
            test   : /\.css$/,
            loader : 'style!css'
        }, {
            test   : /\.less$/,
            loader : 'style!css!less'
        }, {
            test: /\.(png|woff|woff2|eot|ttf|svg)$/,
            loader: 'url?limit=100000'
        }],
...

ViewでBootstrapをロードする

 フロントエンドのパッケージ管理をWebpackとnpmからインストールしました。Bootstrapの.jsと.cssをimport句を使ってエントリーポイントのmain.jsにロードします。

app/client/js/main.js
import m from 'mithril';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.css';
...

NavBarコンポーネント

 BootstrapのNavBarをMithrilのコンポーネントにしたES6で書いてみます。MithrilのViewはmのDSLを使います。ReactのJSXを移植したMSXという名前がノスタルジックなシンタックスもあります。m関数はオブジェクトと配列で仮装DOMを書くことができます。個人的にはJSXスタイルより書きやすいです。

app/client/js/navbar.js
import m from 'mithril';

const NavBar = {
    view: (ctrl, args) => {
        return m('.container', [
                 m('.nav-header', [
                   m('a.navbar-brand', { href: '#' }, args.title)
            ])
        ]);
    }
}

export default NavBar;

NavBarコンポーネントはmain.jsにimportしてm.mountをコールしてindex.htmlのnavbaridにマウントします。m.componentは引数をviewにバインドしたNavBarコンポーネントを返します。ここではタイトルの名称を渡しています。

app/client/js/main.js
...
import NavBar from './navbar';
m.mount(document.getElementById('navbar'),
                                m.component(NavBar, {
                                    title: 'Mithril Study'
                                }));
...

Homeコンポーネント

 Homeコンポーネントも普通のオブジェクトで書きました。Glyphアイコンを使ったBootstrapのアラートです。

app/client/js/home.js
import m from 'mithril';

export default {
    controller: () => {},
    view: () => {
        return [
            m('div', {class: 'container'}, [
                m('.page-header', [
                    m('h3', 'Bootstrap アラート')
                ]),
                m('.alert.alert-info[role="alert"]', [
                    m('span.glyphicon.glyphicon-exclamation-sign[aria-hidden="t\
rue"]'),
                    m('span.sr-only', 'Info:'),
                    ' Infoのサンプルです。'
                ])
            ])
        ];
    }
};

 仮装DOMのDIV要素はm('div')と書いたり、省略してCSSのクラス名をつなげてm('.alert.alert-info[role="alert"]'のようにも書けます。以下のようなHTMLを生成します。

<div role="alert" class="alert alert-info">
  <span aria-hidden="true" class="glyphicon glyphicon-exclamation-sign"></span>
  <span class="sr-only">Info:</span>
 Infoのサンプルです。
</div>

index.html

 SPAのHTMLはとてもシンプルでWebpackのjsをバンドルしてサーブするだけです。html-webpack-pluginを使えばHTMLファイルも生成してくれますが、navbarも別にマウントしたかったのでindex.htmlを用意しました。

dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Mithril Study</title>
    <link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon">
  </head>
  <body>
    <nav id="navbar" class="navbar navbar-default"></nav>
    <div id="root"></div>
    <script src="/js/main.js"></script>
  </body>
</html>

 /js/main.jsはwebpack.config.jsのoutputフィールドに指定したWebpackがバンドルしたjsファイルの出力先になります。filenameを[name].jsとするとエントリポイントに指定したファイル名が[name]で置換されます。

webpack.config.js
...
    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/'
    },
...

ラウター

 エントリポイントのmain.jsを書きます。m.mountコンポーネントを使いNavBarを<nav id="navbar"マウントします。m.routeはURLを使って複数のコンポーネントをマウントできるラウターです。今回はURLは/の1つしかないので、デフォルトの/にHomeコンポーネントをマップします。

<div id="root">をラウター

app/client/js/main.js
import m from 'mithril';
import 'bootstrap';
import Home from './home';
import NavBar from './navbar';
import 'bootstrap/dist/css/bootstrap.css';
import '../css/main.css';

const home = m.component(Home);

m.mount(document.getElementById('navbar'),
                                m.component(NavBar, {
                                    title: 'Mithril Study'
                                }));

m.route(document.getElementById('root'), '/', {
    '/': home
});

テスト

 Expressの開発サーバーはDocker Composeからワンショットのコンテナで起動します。--service-portsフラグを指定すると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

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

 docker-compose.ymlのmithrilサービスの3000ポートをDockerホストの3030ポートにマップしています。

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

 docker-compose psコマンドからコンテナの起動と開いているポートの状態を確認できます。

$ docker-compose ps
       Name               Command       State           Ports
----------------------------------------------------------------------
part1_mithril_run_2   npm run nodemon   Up      0.0.0.0:3030->3000/tcp

クラウドの仮想マシンにパブリックIPアドレスの3030ポートをブラウザで開きます。MithrilでBootstrapを使った簡単なアラート画面が表示されました。

mithril-study-alert.png

9
11
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
9
11