webpack

Node.js 実践講座 事例編 その2 | webpackで静的サイトジェネレータを自作する

More than 1 year has passed since last update.

はじめに

この記事ではNode.jsのモジュールバンドラであるwebpackを使用して静的サイトジェネレータを構築する方法を紹介します。

全体の流れ

全体の流れを下記に示します。

  • ワークスペースの作成
  • package.jsonの作成 | npm init
  • パッケージのインストール | npm install
  • ディレクトリとファイルの作成
  • まずはwebpackを動かす
  • HTML5 Boilerplateで仮組みする
  • Jadeで構造を記述する
  • Lessで外観を記述する
  • JavaScriptで振る舞いを記述する
  • Gulpでビルド手順を記述する
  • Connectで成果物を確認する

ワークスペースの作成

コマンドを下記にし示します。

mkdir -p ~/workspace/js/practice/static-site-generator
cd ~/workspace/js/practice/static-site-generator

package.jsonの作成 | npm init

コマンドを下記に示します。

npm init -f

パッケージのインストール | npm install

コマンドを下記に示します。

npm install --save-dev connect css-loader gulp gulp-jade jade jade-loader less-loader rimraf serve-static style-loader webpack webpack-dev-server

ディレクトリとファイルの作成

コマンドを下記に示します。

mkdir bin
mkdir public
mkdir src
mkdir src/style
mkdir src/templates
mkdir src/templates/about
mkdir src/templates/layouts
touch bin/www
touch src/style/main.less
touch src/style/sub.less
touch src/templates/about/index.jade
touch src/templates/layouts/default.jade
touch src/templates/index.jade
touch src/entry-dev.js
touch src/entry.js
touch gulpfile.js
touch webpack.config.js

まずはwebpackを動かす

webpack.config.jsの編集

webpack.config.jsの内容を下記に示します。

webpack.config.js
'use strict';

var path = require('path')

module.exports = {
  entry: './src/entry-dev.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'js/bundle.js',
  },
  module: {
    loaders: [
      { test: /\.jade$/, loader: 'jade' },
      { test: /\.less$/, loader: 'style!css!less' },
    ],
  },
}

package.jsonの編集

package.jsonを内容を下記に示します。

package.json
{
  "name": "static-site-generator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node_modules/.bin/webpack-dev-server --inline --history-api-fallback --content-base public --port 8080",
    "build": "node_modules/.bin/gulp build",
    "serve": "node bin/www",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "connect": "^3.4.0",
    "css-loader": "^0.23.1",
    "gulp": "^3.9.0",
    "gulp-jade": "^1.1.0",
    "gulp-less": "^3.0.5",
    "jade": "^1.11.0",
    "jade-loader": "^0.8.0",
    "less-loader": "^2.2.2",
    "rimraf": "^2.4.5",
    "serve-static": "^1.10.0",
    "style-loader": "^0.13.0",
    "webpack": "^1.12.9",
    "webpack-dev-server": "^1.14.0"
  }
}

webpack-dev-serverの起動

コマンドを下記に示します。

npm start

動作確認

ブラウザで http://127.0.0.1:8080/ へアクセスします。現在はpublicディレクトリに何もないので404 Not Foundが表示されます。

HTML5 Boilerplateで仮組みする

ダウンロード

  1. http://www.initializr.com/ にアクセスします
  2. 1 - Pre ConfigurationでBootstrapをクリックします
  3. 2 - Fine Configurationで下記を選びます
    • HTML/CSS Template: Twitter Bootstrap
    • HTML5 Polyfils: Modernizr (Respondのチェックを外します)
    • jQuery: Minified
    • H5BP Optional: すべてのチェックを外します。
  4. Download it! ボタンをクリックします
  5. ZIPファイルのダウンロードが開始します

展開

ダウンロードしたZIPをpublicディレクトリ内に展開します。

動作確認

ブラウザで http://127.0.0.1:8080/ へアクセスするとBootstrapのページが表示されます。

Jadeで構造を記述する

index.htmlの編集

public/index.htmlの内容を下記に示します。

public/index.html
<!doctype html>
<html class="no-js" lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="/css/main.css">

    <!-- separator -->

    <script src="/js/vendor/modernizr-2.8.3.min.js"></script>
  </head>
  <body>

    <!-- separator -->

    <main></main>

    <script src="/js/vendor/jquery-1.11.2.min.js"></script>
    <script src="/js/vendor/bootstrap.min.js"></script>
    <script src="/js/bundle.js"></script>

    <!-- separator -->

  </body>
</html>

entry-dev.jsの編集

entry-dev.jsの内容を下記に示します。

src/entry-dev.js
'use strict';

require('./entry')
require('./style/main.less')

var context = require.context('./templates/', true, /\.jade$/)

var path = './' + window.location.pathname.slice(1) + 'index.jade'
var template = context(path)

var context = {
  baseUrl: '',
  parts: [],
}

$('main').html(template(context))

テンプレートの編集

src/templates/ディレクトリ内のabout/index.jadelayouts/default.jadeindex.jadeの内容を下記に示します。

src/templates/about/index.jade
extends ../layouts/default

block content
  .container
    p This is about page.

    .row
      .col-md-offset-3.col-md-6
        a.btn.btn-default.btn-block(href='../') Back
src/templates/layouts/default.jade
!= parts[0]

block style

!= parts[1]

nav.navbar.navbar-default.navbar-static-top
  .container
    .navbar-header
      a.navbar-brand(href='#') Project name

block content

.container
  hr
  footer
    p &copy; Company 2015

!= parts[2]

block script

!= parts[3]
src/templates/index.jade
extends ./layouts/default

block content
  .jumbotron
    .container
      h1 Hello, world!
      p
        | This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.
      p
        a.btn.btn-primary.btn-lg(href='#{baseUrl}/about/', role='button') Learn more »
  .container
    .row
      .col-md-4
        h2 Heading
        p
          | Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
        p
          a.btn.btn-default(href='#', role='button') View details »
      .col-md-4
        h2 Heading
        p
          | Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.
        p
          a.btn.btn-default(href='#', role='button') View details »
      .col-md-4
        h2 Heading
        p
          | Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
        p
          a.btn.btn-default(href='#', role='button') View details »

動作確認

ブラウザで http://127.0.0.1:8080/ へアクセスするとIndexページが表示されます。ジャンボトロン内のLearn MoreをクリックするとAboutページへ移動します。AboutページのBackボタンをクリックするとIndexページへ移動します。

Lessで外観を記述する

main.lessの編集

main.lessの内容を下記に示します。

src/style/main.less
@import "./sub.less";

sub.lessの編集

sub.lessの内容を下記に示します。

src/style/sub.less
body {
  background: gray;
}

動作確認

ブラウザで http://127.0.0.1:8080/ へアクセスすると灰色背景のIndexページが表示されます。

JavaScriptで振る舞いを記述する

JavaScriptで振る舞いを記述する方法は2つあります。

  • entry.jsに振る舞いを記述する
  • Jadeファイルのscriptブロックに振る舞いを記述する

個人的にはentry.jsに関数やクラスを記述してJadeファイルのscriptブロックからそれらを呼び出す書き方が好みです。

src/entry.js
'use strict';

window.printHelloWorld = function () {
  console.log('Hello, world!')
}
src/templates/about/index.jade
'use strict';

extends ../layouts/default

block content
  .container
    p This is about page.

    .row
      .col-md-offset-3.col-md-6
        a.btn.btn-default.btn-block(href='../') Back

block script
  script.
    (function () {
      'use strict';

      printHelloWorld()
    })();

Gulpでビルド手順を記述する

gulpfile.jsの編集

gulpfile.jsの内容を下記に示します。

gulpfile.js
'use strict';

var fs = require('fs')
var path = require('path')
var gulp = require('gulp')
var rimraf  = require('rimraf')
var webpack = require('webpack')
var gulpJade = require('gulp-jade')
var gulpLess = require('gulp-less')
var webpackConfig = require('./webpack.config')

gulp.task('clean', function (cb) {
  rimraf('./dist/', cb)
})

gulp.task('copy', ['clean'], function () {
  return gulp.src([
    './public/**',
    '!./public/index.html',
    '!./public/css/main.css',
  ])
    .pipe(gulp.dest('./dist/'))
})

gulp.task('jade', ['clean'], function () {
  var root = fs.readFileSync(path.join(__dirname, './public/index.html')).toString()
  var parts = root.replace('<main></main>', '').split('<!-- separator -->')

  return gulp.src([
    './src/templates/**/*.jade',
    '!./src/templates/layouts/**/*.jade',
  ])
    .pipe(gulpJade({
      locals: {
        baseUrl: '',
        parts: parts,
      },
    }))
    .pipe(gulp.dest('./dist/'))
})

gulp.task('less', ['clean'], function () {
  return gulp.src('./src/style/main.less')
    .pipe(gulpLess())
    .pipe(gulp.dest('./dist/css/'))
})

gulp.task('webpack', ['clean'], function (cb) {
  webpackConfig.entry = './src/entry.js',
  webpackConfig.plugins = (webpackConfig.plugins || []).concat([
    new webpack.optimize.UglifyJsPlugin(),
  ])

  webpack(webpackConfig, cb)
})

gulp.task('build', [
  'clean',
  'copy',
  'jade',
  'less',
  'webpack',
])

gulp.task('default', ['build'])

ビルド

コマンドを下記に示します。

npm run build

ビルドが成功するとdist/ディレクトリが生成されます。

Connectで成果物を確認する

bin/wwwの編集

bin/wwwの内容を下記に示します。

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

'use strict';

var port = Number.parseInt(process.env.PORT || '3000', 10)

var http = require('http')
var path = require('path')
var connect = require('connect')
var serveStatic = require('serve-static')

var app = connect()
app.use(serveStatic(path.join(__dirname, '../dist')))

var server = http.createServer(app)
server.listen(port)

server.on('listening', function () {
  console.log('Listening on ' + port)
})

サーバ起動

コマンドを下記に示します。

npm run serve

動作確認

ブラウザで http://127.0.0.1:3000/ へアクセスします。http://127.0.0.1:8080/ と同じように表示されたら成功です。

おわりに

webpackの豊富なローダーを使用すれば、Lessの代わりにSass/Scssで外観を記述したり、JavaScriptの代わりにCoffeeScriptで外観を記述したりすることが比較的簡単にできます。ご自身の状況に合わせてぜひとも色々とカスタマイズして役立てていただければ幸いです。

次回はElectron.jsを使用してWebアプリケーションをデスクトップアプリケーションにする方法を紹介する予定です。