Webpack 4 + Babel + Sass 環境を構築

  • CSS
  • Javascript

webpack 4 で sass と Javascript のコンパイルをする方法のまとめです。webpack は js ファイル一つに全てをまとめて、css ファイルの link タグも js 生成することが多いのかもしれませんが、読み込まれるまで一瞬サイトが崩れて見えてしまい、対処法がわかりませんでした…。

そのため、今回は css と js ファイルそれぞれ別で出力するようにします。

ディレクトリ構成

src/を開発用ディレクトリとして進めていきます(名前は任意で変えてください)。src/の中には下記のファイルの他にnode_modules/なども入ってくる形です。実際に使う js と css のファイルはdist/に出力させます。

/
├ src/
│ ├ sass/
│ │ ├ index.scss
│ │ └ 他のsassファイルいれとくフォルダなど/
│ └ js/
│   └ index.js
└ dist/
  ├ index.css
  └ index.js

インストール

src/へ移動して実行します。下記コマンドを実行する前に、yarn のインストールはしておきましょう。

初期化

yarn init

webpack

yarn add -D webpack webpack-cli

babel

yarn add -D @babel/core @babel/preset-env babel-loader

sass/css

yarn add -D node-sass sass-loader style-loader css-loader postcss-loader autoprefixer mini-css-extract-plugin
  • node-sass … sass を処理するためのもの?
  • sass-loader … sassファイルをロードする
  • style-loader … js で styleタグ生成(今回は別ファイルに出力するので使いませんが、一応入れときます)
  • css-loader … cssファイルをロードする
  • postcss-loader … 古いブラウザにもモダンCSSを適用できるようにするなど
  • autoprefixer … ベンダープレフィックス自動付与
  • mini-css-extract-plugin … cssファイル圧縮、別ファイルで出力する

file

使い方が分からなかったので今回は使用してませんが、一応入れときます。

yarn add -D file-loader
  • file-loader … 画像やフォントなどのソースファイルをロードする

glob

sass ファイル内で @import するときに*(アスタリスク)を使用できるようにするため

yarn add -D import-glob-loader

Typescript

typescript を使いたいなら入れておきます。

yarn add -D typescript ts-loader

設定

大まかに説明すると、index.css と index.js の2ファイルを出力します。webpack ではメインのエントリーポイントに指定した index.js に css をまとめる手法がよくある(?)のですが、今回は分割します。

※Typescript の設定も書いていますが、正しいか不明なのであまり参考にしないでください…!

webpack.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, argv) => {

  // 開発モードかどうか
  const IS_DEVELOPMENT = argv.mode === 'development';

  return {

    // モード(none / production / development)
    mode: argv.mode,

    // 開発パス
    entry: {
        'index': './js/index.js',
    },

    // development ならソースマップ表示
    devtool: IS_DEVELOPMENT ? 'source-map' : 'none',

    // 出力パス
    output: {
      filename: '[name].js',
      path: path.resolve(__dirname, '../dist'),
    },

    plugins: [
      new MiniCssExtractPlugin({
        filename: '[name].css',
        path: path.resolve(__dirname, '../dist'),
      })
    ],

    // import 文で .ts ファイルを解決
    resolve: {
      extensions: [".ts"]
    },

    module: {
      rules: [
        // TS
        {
          // 拡張子 .ts の場合
          test: /\.ts$/,
          // TypeScript をコンパイルする
          use: "ts-loader"
        },
        // JS
        {
          test: /\.js$/,
          use: [
            {
              loader: "babel-loader",
              options: {
                presets: [
                  // ES5 に変換
                  "@babel/preset-env"
                ]
              }
            }
          ]
        },
        // SASS
        {
          test: /index\.scss$/,
          use: [
            // css ファイル生成
            MiniCssExtractPlugin.loader,
            // css コンパイル
            {
              loader: 'css-loader',
              options: {
                // CSS内のurl()メソッド許可 / 禁止
                url: false,
              }
            },
            // ベンダープレフィックス
            {
              loader: "postcss-loader",
              options: {
                plugins: [
                  require("autoprefixer")({
                    grid: true,
                  })
                ]
              }
            },
            // sass コンパイル
            'sass-loader',
            // sass ファイル内で glob 有効化
            'import-glob-loader'
          ],
        },
      ],
    },
  }
};

env と argv

引数に入れた argv.mode で現在のモードを取得することができます(モードについては後に書く package.json の引数のオプション--modeの部分を見てください)。

これにより、例えば development モードのときは開発中なので css を圧縮せずに詳細を見れるようにして、production モードの時は css を圧縮してファイル容量を減らすなどの設定を動的にできるようになります。今回 return しているのはこの現在の mode を定数に入れておくためです。

entry

出力するファイルのパス

devtool

ソースマップの設定。ここを見るといろいろあるけどよくわかってません…今回は development モードのときは source-map を生成します。

output

  • filename … 出力ファイル名。[name] で entry のキー名を引き継ぐ
  • path … 絶対パスでディレクトリを指定する

plugins

  • MiniCssExtractPlugin … css の出力先。output の設定と同じ。

modules.rules

  • test … 対象のファイル
  • use … インストールしたモジュールを適用する。下から上に適用されるので、順番が重要。

index.scss

index.scssから全てのsassファイルを@importさせて読み込みます。そのため webpack.config.json のrulestestに記述するパスはindex.scssのみとしています。glob がないと1ファイルごと記述しなくてはなりませんが、*(アスタリスク)をワイルドカードとして使えます。

@import 'path/*'; // 直下にある sass ファイル全て
@import 'path2/*';
@import 'path3/**'; // ディレクトリの階層があるならアスタリスク2つ

本当はentry: { ... }に指定している index.js でrequire('path/*')のようにし glob を使って一括読み込みをしたかったんですが、読み込み順がバラバラになってしまって上手くいきませんでした…。自分で解決できなさそうだったので、仕方なく index.scss という読み込み専用ファイルを作ることにしました。

index.js

css ファイルを読み込んでいるだけです。

// sass ファイル読み込み
import "../sass/main.scss"

package.json

{
  ...
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production",
    "watch": "webpack --mode development --watch --color --progress"
  },
  "browserslist": [
    "last 1 version",
    "> 1%",
    "IE 10"
  ]
  ...
}

scripts

yarn runで実行するスクリプトです。下記で実行します。

本番環境用
yarn run build
開発環境用
yarn run dev
開発環境用(監視)
yarn run watch

browserslist

autoprefixer とか babel とかを使ってコンパイルするときに対応させるブラウザ。ここに色々書いてあるので、任意で設定してください。

参考

https://qiita.com/t-motoki/items/4eab2fcd46198c2377b3
https://www.expexp.jp/webpack4-2019/
https://qiita.com/soarflat/items/28bf799f7e0335b68186