かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Webpack Babel + Pug + Stylus で ホットリロードできるSPA開発環境作った。

react-create-app とかボイラーテンプレートでサクッとできる時代だけど、自分で作ってみて学ぼうと思ったので、秘伝のタレ作ってた 🍲

構成
/build ... ビルドされたファイルが出力される
/src
  |- /stylus
  |     |- App.styl
  |- index.js
  |- index.pug
package.json

環境

@babel/core: 7.4.5
webpack: 4.34.0
webpack-dev-server: 3.7.1
# plugins
html-webpack-plugin: 3.2.0
mini-css-extract-plugin: 0.7.0
clean-webpack-plugin: 3.0.0

つくったのよこせ!

はい。

TL;DR 秘伝のタレレシピ

Babel

install

$ yarn add -D @babel/core @babel/cli @babel/preset-env core-js@3
$ yarn add @babel/polyfill

設定ファイル babel.config.js を作成して設定を書いていく。
複雑なことはせずにシンプルにES6使えるようにする感じで。

babel.config.js

const presets = [
 ["@babel/preset-env", {
  "modules": false,
  "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"],
  "useBuiltIns": "usage",
  "corejs": 3,
 }],
];

module.exports = { presets };

useBuiltIns を使っている時に core-js のバージョン指定がないと次のようなWARNINGが表示される

WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.

You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:

  npm install --save core-js@2    npm install --save core-js@3
  yarn add core-js@2              yarn add core-js@3

core-jsのバージョン指定が無いと2系を指定するけど、将来バージョンが変更になるかもって感じっぽい

Webpack

install

$ yarn add -D webpack webpack-cli webpack-dev-server babel-loader

build コマンドを作成

build する npm script を package.json に追加する

{
  // ... 略
  "scripts": {
    "build": "./node_modules/.bin/webpack --mode production",
    "build:dev": "./node_modules/.bin/webpack --mode development"
  },
  // ... 略
}

設定ファイルの作成

webpack.config.js を作成して設定を追加していく

webpack.config.js

const path = require('path');
// PATH
const ASSET_PATH = process.env.ASSET_PATH || '/';
const src = path.join(__dirname, 'src');
const entrypoint = {
  // app.js という名前で出力する
  app: path.join(src, 'index.js'),
};
const output = path.resolve(__dirname, 'build');

module.exports = (env, argv) => {
  return {
    mode: 'development',
    entry: entrypoint,
    output: {
      path: output,
      publicPath: ASSET_PATH,
      filename: '[name].js',
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: [
            { loader: "babel-loader", }
          ],
        },
      ],
    },
    devtool: 'source-map',
  };
};

eslint とか走ってないけど、とりまモダンなJS出かける環境の設定ができました。

webpack + pug

install

$ yarn add -D pug pug-loader html-webpack-plugin
note. HtmlWebpackPlugin

webpackでbundle したJavaScriptをscriptタグで読み込んだHTMLを生成するプラグインtemplateオプションで pug ファイルなどを生成するHTMLのテンプレートファイルを指定できる。

ref.

pug の設定

webpack.config.js

const path = require('path');
// Plugins
// HTMLを生成するプラグイン
const HtmlWebpackPlugin = require('html-webpack-plugin');
// PATH
// 略
// html template
const htmlTemplate = path.join(src, 'index.pug');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';

  return {
    mode: 'development',
    entry: entrypoint,
    output: {
      path: output,
      publicPath: ASSET_PATH,
      filename: '[name].js',
    },
    module: {
      rules: [
        // 略
        {
          test: /\.pug$/,
          use: {
            loader: 'pug-loader',
            // production mode の時 minify する
            options: !isProduction ? {
              pretty: true
            } : {}
          }
        },
      ],
    },
    devtool: 'source-map',
    plugins: [
      new HtmlWebpackPlugin({
        template: htmlTemplate,
      }),
    ],
  };
};

Template src/index.pug

doctype html
head
  title My HIDEN no TARE!
body
  h1 秘伝のタレ

👇 webpack

$ yarn run build

/build ディレクトリ内にHTMLとJavaScriptファイルが生成される。 HTMLファイルには下記のscriptタグが自動的に挿入されている。

<script type="text/javascript" src="/app.js"></script>

webpack + stylus

install

$ yarn add -D stylus stylus-loader css-loader mini-css-extract-plugin
note. css-loader

importurl の解決などCSSの依存解決を行う loader

ref. GitHub - webpack-contrib/css-loader: CSS Loader

note.

JavaScriptにbundleされるCSSCSSファイルに変換するプラグイン

ref.

stylus の設定

webpack.config.js

const path = require('path');
// Plugins
const HtmlWebpackPlugin = require('html-webpack-plugin');
// CSSファイルを出力するプラグイン
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 略

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  const enabledSourceMap = isProduction || false;
  
  const getStyleLoaders = () => {
    const loaders = [
      {
        loader: MiniCssExtractPlugin.loader,
      },
      {
        loader: 'css-loader',
        options: {
          // production mode の時 source map を出力する
          sourceMap: enabledSourceMap,
          // 事前に適応するloaderの数
          importLoaders: 1
        },
      },
      {
        loader: 'stylus-loader',
        options: {
          // stylus では source map を作らない
          sourceMap: false,
        },
      },
    ];
    return loaders; 
  };

  return {
    // 略
    resolve: {
      // import を js ファイル以外でも使えるようにする
      modules: ['node_modules'],
      // import できる拡張子を指定
      extensions: ['.json', '.js', '.jsx', '.styl'],
    },
    module: {
      rules: [
        // 略
        {
          test: /\.styl$/,
          use: getStyleLoaders(),
          // CSS は Webpack の Tree Shaking での自動的削除は
          // 問題がある可能性があるので sideEffects を true にする
          sideEffects: true,
        },
      ],
    },
    devtool: enabledSourceMap && 'source-map',
    plugins: [
      new HtmlWebpackPlugin({
        template: htmlTemplate,
      }),
      new MiniCssExtractPlugin({
        // stylesheets ディレクトリの中に出力させる
        filename: "stylesheets/[name].css",
      }),
    ],
  };
};

webpack の loader は下から順番に実行されるので CSS の処理は次のような感じ

  1. stylus-loader で stylus を コンパイル
  2. css-loaderCSS の依存を解決し JS ファイルに bundle
  3. MiniCssExtractPlugin.loader で JS に bundle された CSSCSSファイルにして出力
CSSの出力

webpack.config.jsresolve の設定を追加し stylus ファイルを import 出来るようにする。

module.exports = (env, argv) => {
  return {
    // ...
    resolve: {
      // import を js ファイル以外でも使えるようにする
      modules: ['node_modules'],
      // import できる拡張子を指定
      extensions: ['.json', '.js', '.jsx', '.styl'],
    },
   // ...

※ webpack 4 では拡張子の指定に extensions: ['', '.json', '.js', '.jsx', '.styl'] のように空文字('')があるとエラーになる

エントリーポイントになっているsrc/index.jsに読み込ませたい stylus ファイルを読み込ませる。

import './stylus/App.styl';

👇 wepack

$ yarn run build

/build/stylesheets 内に app.cssapp.css.map が生成される。 出力されるHTMLはheadタグ内に自動的に下記のような<link>が作られ css が読み込まれるようになっている

<link href="/stylesheets/app.css" rel="stylesheet">

PostCSS で 自動的にベンダープレフィックスを付けたりよしなにする

stylus の nib を使ってもよいのですが、最近は PostCSS を使ってよしなにするのがナウいらしいので、PostCSSを使ってベンタープレフィックスと nomalize 、メディクリの統合・productionモードの際にminifyをしたいと思います。 (Prettierでコードフォーマットを揃えるのはまたいつか)

install

$ yarn add -D postcss-loader postcss-preset-env postcss-flexbugs-fixes postcss-normalize css-mqpacker cssnano
  • postcss-loader ... PostCSSをwebpackで使うためのloader
  • postcss-preset-env ... モダンCSSをconvertするパッケージ。対応ブラウザに合わせたautoprefixの指定などもできる
  • postcss-flexbugs-fixes ... display: flex のブラウザのバグ対応したCSSを追加してくれる
  • postcss-normalize ... browsersで指定した対応ブラウザに合わせて必要な normalize.css を追加する
  • css-mqpacker ... mediaクエリをまとめるパッケージ。デファクトスタンダードっぽいけど、現在メンテがされなくて deprecated になっているので将来的にforkされたべつのパッケージになるかも
  • cssnano ... CSSをminifyするライブラリ

PostCSS の設定

webpack.config.js

// 略
// CSS
// normalize.css を追加
const postcssNormalize = require('postcss-normalize');
// media query をまとめる
const cssMqpacker = require('css-mqpacker');
// css minify
const cssnano = require('cssnano');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  const enabledSourceMap = isProduction || false;
  
  const getStyleLoaders = () => {
    const loaders = [
      {
        loader: MiniCssExtractPlugin.loader,
      },
      {
        loader: 'css-loader',
        options: {
          sourceMap: enabledSourceMap,
          // stylus と PostCSS の2つ
          importLoaders: 2
        },
      },
      {
        loader: 'postcss-loader',
        options: {
          plugins: [
            // production mode の時 minify
            isProduction && cssnano({ preset: 'default' }),
            cssMqpacker({
              sort: true
            }),
            require('postcss-flexbugs-fixes'),
            require('postcss-preset-env')({
              autoprefixer: {
                flexbox: 'no-2009',
              },
              stage: 3,
            }),
            postcssNormalize({
              browsers: [">0.25% in JP", "not ie <= 10", "not op_mini all"],
            }),
          ].filter(Boolean), // false を削除
          sourceMap: enabledSourceMap,
        }
      },
      {
        loader: 'stylus-loader',
        options: {
          sourceMap: false,
        },
      },
    ];
    return loaders; 
  };

  return {
    // 略
    module: {
      rules: [
        // 略
        {
          test: /\.styl$/,
          use: getStyleLoaders(),
          sideEffects: true,
        },
      ],
    },
    // 略
  };
};

build 時に出力先のディレクトリを cleanup する

development mode の時は source map を出力しないようにしているので、build した際に不要なファイルが残っていないようにclean-webpack-plugin プラグインを使って、buildが走る前に出力先buildディレクトリの中をcleanupする。

install

$ yarn add -D clean-webpack-plugin

clean-webpack-plugin の設定

webpack.config.js

const path = require('path');
// plugins
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 略

module.exports = (env, argv) => {
  // 略
  return {
    // 略
    plugins: [
      new CleanWebpackPlugin({
        // 実際に削除してしまう前にテストする場合は true にする
        dry: false,
        // 削除するファイル名をconsoleに出力
        verbose: true,
        // static なファイルを置いておく可能性があるので img ディレクトリ内のファイルは削除しない
        cleanOnceBeforeBuildPatterns: ['**/*', '!img', '!img/*']
      }),
      // 略
    ],
  };
};

👇 動作確認

$ yarn run build
$ yarn run build:dev

production mode で build した後に development mode で build して app.css.map など不要なファイルが消えていればOK

ホットリロード可能な確認環境 webpack-dev-server

webpackでwatchできるファイルを監視してホットリロードできるとても便利なサーバー

install

$ yarn add -D webpack-dev-server

webpack-dev-server の設定

open オプションで自動的にブラウザを開く機能があるが、常に新しくページを開かれるのもストレスなのでオプションで新しく開くかどうか選べるようにします。

package.json にサーバー起動の npm script を追加

//略
  "scripts": {
    "start": "./node_modules/.bin/webpack-dev-server --mode development --hot --env.open",
    "restart": "./node_modules/.bin/webpack-dev-server --mode development --hot",
    "build": "./node_modules/.bin/webpack --mode production",
    "build:dev": "./node_modules/.bin/webpack --mode development"
  }

start なら新しくブラウザを開き、restart なら開かない

webpack.config.js

// 略
module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  const enabledSourceMap = isProduction || false;
  const isOpenDevServer = !!env && env.open;
  // 略
  return {
    // 略
    devServer: {
      open: isOpenDevServer,
      port: 3000,
      watchContentBase: true,
    }
  };
};

👇 動作確認

$ yarn run start

localhost:3000 でページが起動し、javascript, pug, stylus を変更でホットリロードされていればOK

 

ポエム

フレームワークのお作法で開発するなら必要のない部分かもですが、秘伝のタレを自分で作ってみることによって理解が深まったように思います。 特に loader は受け取ったものを JS で返し、plugin は受け取ったものを自由な形式で返せる という違いが発見できたことは学びでした。

昨今のフロントエンドまわりパッケージが乱立しててどれを選べば良いのか…ってのが難しい。
一時期より動きは穏やかになったとは言え、まだまだ変化の早めな界隈な感じなので下手に選ぶとすぐ使えなくなったり、更新が止まって廃止とかになったりするので調査に時間がかかる感は否めないです。(Qiitaとか、日付のないblogとかたくさん検索にかかるので余計に検索に時間が…


[参考]

pug

css

PostCSS

デザイニングWebアクセシビリティ - アクセシブルな設計やコンテンツ制作のアプローチ

デザイニングWebアクセシビリティ - アクセシブルな設計やコンテンツ制作のアプローチ

秘伝 焼肉のたれ 575g×2本

秘伝 焼肉のたれ 575g×2本

\秘伝の🌟タレ/