middlemanとwebpackでモダンな静的サイトのテンプレートを作る

middleman + webpackな最新の静的サイトテンプレートを作ってみます。

ちなみに今回作ったものはこちらのリポジトリにpushしておきました。バージョンなどの細かいライブラリはリポジトリ見ればわかるようになっています。

t4traw/middleman-webpack-sample

2019年6月更新

webpackのバージョンと、軽量化・高速化などを視野に入れて、更新しました。

📎 今回やったこと

  • middleman + webpackのセットアップ
  • cssを別ファイルで出力する
  • autoprefixerでprefixを付ける
  • cssをminifyする
  • 使っていないスタイルを削除する

🌱 事前準備

今回はwebpackを使うので、nodejsが必要です。また僕はyarnを使用したので、yarnもインストールが必要です。Mac環境の方はbrewからサクっとインストールしてください。

$ brew intall node
$ brew intall yarn

🔧 middlemanとwebpackをインストール

まずはGemfileをサクっと準備。

$ mkdir YOUR_SITE_NAME && cd $_
$ bundle init

Gemfileの中にmiddlemanを書き込みます。

Gemfile
gem "middleman"

そしてbundle installします。

$ bundle install

middlemanがインストールできたら、middleman newします。

$ middleman new .

この段階で.gitignoreが生成されますが、node_modulesなどのファイルが入っていないので、サクっと.gitignoreを生成しなおします。

$ rm -f .gitignore && curl https://www.gitignore.io/api/node%2Csass%2Cruby%2Cmacos%2Cgrunt > .gitignore

webpackを使うためにpackage.jsonを用意します。yarn initする時にいくつか質問がありますが、とりあえず全部Enterで大丈夫だと思います。必要があれば変えてください。

$ yarn init

package.jsonができた事を確認したら、必要なnpmパッケージをインストールします。まずはwebpack。そしてwebpack.config.jsを生成しておきます。

$ yarn add -D webpack
$ touch webpack.config.js

この段階でまずはmiddlemanのexternal_pipeline(外部パイプライン)でwebpackが動くか確認してみます。

まずmiddlemanのconfigにexternal_pipelineを使うように書き、webpackのコマンドを追加します。アセットの取得先をsource: ".tmp/dist"にしておくのがポイントです。

config.rb
activate :relative_assets
set :relative_links, true

activate :external_pipeline, {
  name: :webpack,
  command: build? ?
    "NODE_ENV=production yarn run build" :
    "NODE_ENV=develop yarn run develop",
  source: ".tmp/dist",
  latency: 1
}

そして、設定したコマンドをpackage.jsonに追加します。

package.json
{
  "name": "middleman-webpack-sample",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "ssh://git@github.com/t4traw/middleman-webpack-sample.git",
  "author": "Tatsuro Moriyama <t4traw@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^4.17.1"
  },
  "scripts": {
    "develop": "webpack --config ./webpack.config.js --watch -d --progress --colors",
    "build": "webpack --config ./webpack.config.js --bail -p"
  }
}

最後に、webpackの設定をします。middlemanのexternal_pipelineのアセットの場所をsource: ".tmp/dist"にしたので、webpackのoutputもそこにしておきましょう。

webpack.config.json
const webpack = require('webpack');
module.exports = {
  entry: [
    './source/javascripts/site.js'
  ],
  output: {
    path: __dirname + '/.tmp/dist',
    filename: 'javascripts/bundle.js'
  }
};

各設定ファイルを上記のようにし、とりあえずmiddlemanを起動してみます。

$ middleman

↓みたいな感じで表示されればひとまず成功です。

== The Middleman is loading
== Executing: `NODE_ENV=develop yarn run develop`
== View your site at "//localhost:4567", "//127.0.0.1:4567"
== Inspect your site configuration at "//localhost:4567/__middleman", "//127.0.0.1:4567/__middleman"
== External: yarn run v1.3.2
== External: $ webpack --config ./webpack.config.js --watch -d --progress --colors
 10% building modules 1/1 modules 0 active== External: Webpack is watching the files…
== External: Hash: 56b513b6e7776f8a5874
== External: Version: webpack 3.10.0
== External: Time: 106ms
== External:                 Asset     Size  Chunks             Chunk Names
== External: javascripts/bundle.js  3.57 kB       0  [emitted]  main
== External:    [0] multi ./source/javascripts/site.js 28 bytes {0} [built]
== External:    [1] ./source/javascripts/site.js 32 bytes {0} [built]

起動の確認ができたので、あとはjavascriptの読み込みをlayoutに書きます。

source/layouts/layout.erb
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Use the title from a page's frontmatter if it has one -->
    <title><%= current_page.data.title || "Middleman" %></title>
    <%= stylesheet_link_tag "site" %>
    <%= javascript_include_tag "site" %>
  </head>
  <body>
    <%= yield %>
  </body>
  <%= javascript_include_tag :bundle %>
</html>

ここまで下準備。webpack.config.jsonのoutputに書きましたが、filename: 'javascripts/bundle.js'としているので、bundle.jsを読み込めば、すべてjavascriptが読み込まれるようにします。

🚘 webpackでcssをいい感じにする

当初はstyle-loaderでcssもjavascriptで描画していましたが、軽量化や高速化をするときに別で出力する必要がでてきました。なので、sass->cssのコンパイルをした後にcssファイルを出力する方法を書きます。

まずは必要なものをインストールします。

$ yarn add -D node-sass sass-loader css-loader mini-css-extract-plugin

そしてwebpack.config.jsにcss周りの設定を書きます。

webpack.config.json
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  entry: [
    './source/javascripts/site.js'
  ],
  output: {
    path: __dirname + '/.tmp/dist',
    filename: 'javascripts/bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(css|sass|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "sass-loader"
        ]
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ]
}

そして、source/javascripts/site.jsにcssファイルを読み込み設定を書きます。

source/javascripts/site.js
import '../stylesheets/site.css.scss';

これでsassからコンパイルしたmain.cssが生成されます。

最後にsource/layouts/layout.erbへmain.cssを読み込む設定を書きます。

source/layouts/layout.erb
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Use the title from a page's frontmatter if it has one -->
    <title><%= current_page.data.title || "Middleman" %></title>
  </head>
  <body>
    <%= yield %>
  </body>
  <%= stylesheet_link_tag :main %>
  <%= javascript_include_tag :bundle %>
</html>

autoprefixerとminify(csswring, cssnano)を導入する

ブラウザ互換性のprefix込みのスタイルはもう人間がやる事ではないので、autoprefixerを導入します。

あと、cssファイルのムダな空白や改行を削除する=minifyも一緒にやっちゃいます。

$ yarn add -D postcss-loader autoprefixer csswring cssnano

webpack.config.jsonにpostcss-loaderを追加します。

webpack.config.json
〜〜〜省略〜〜〜
  module: {
    rules: [
      {
        test: /\.(css|sass|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: 'postcss-loader',
              options: {
                sourceMap: true,
                plugins: [
                  require('autoprefixer')({
                    grid: true
                  }),
                  require('csswring')(),
                  require('cssnano')({
                    preset: 'default',
                  }),
                  
                ]
              },
          },
          'sass-loader',
        ]
      },
    ]
  }

テストでsource/stylesheets/site.css.scssにdisplay: flex;を追加してみてみます。

source/stylesheets/site.css.scss
.foo {
  display: flex;
}

無事minifyとベンダープレフィックスが追加されています👏

importに*が使えるimport-glob-loaderを導入する

scssでわざわざ正確なファイルパスを書くのは非常に面倒なので、ワイルドカード(*)が使えるimport-glob-loaderを導入します。

$ yarn add -D import-glob-loader

さきほどのcss-loaderなどとは別のブロックに追加します。

webpack.config.json
  module: {
    rules: [
      {
        test: /\.(css|sass|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: 'postcss-loader',
              options: {
                sourceMap: true,
                plugins: [
                  require('autoprefixer')({
                    grid: true
                  }),
                  require('csswring')(),
                  require('cssnano')({
                    preset: 'default',
                  }),
                  
                ]
              },
          },
          'sass-loader',
        ]
      },
      {
        test: /\.(js|sass|scss)$/,
        enforce: "pre",
        loader: 'import-glob-loader'
      },

      // ~~~省略~~~

これで、site.css.scssに

@charset "utf-8";

@import "preload/*";
@import "library/*";
@import "components/*";

みたいな感じで雑にディレクトリを指定し、ディレクトリ内のscssファイルはすべてimportする事ができます。

使用していないcssを削除するPurgeCSSを導入する

最近はcssフレームワークを使わずに制作・開発する事はなくなりました。で、cssフレームワークを使うとどうしてもファイルが大きくなってしまう問題があります。

なので、自動的に使用していないスタイルを削除するPurgeCSSを導入します。

$ yarn add -D purgecss-webpack-plugin
webpack.config.json
const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')

module.exports = {
  // ~~省略~~

    plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgecssPlugin({
      paths: glob.sync(`${path.join(__dirname, 'source')}/**/*`, { nodir: true }),
    }),
  ]
}

これだけで使わないスタイルを削除してくれます。

🎉 StaticSiteBootstrapを更新しておきました。

今回のmiddleman + webpackをStaticSiteBootstrapに反映させておきました。

こうしてGitHubにpushしておけば、

$ middleman init -T t4traw/static_site_bootstrap

あとはいくつかのコマンドで簡単にテンプレートから生成する事ができます。よく静的サイトを制作する方はこんな具合でサクっとベースを作れると楽に走り出せるのでオススメです🏃