Webpack 入門 その2 : SCSS のトランスパイル・Autoprefixer による Browserslist 参照

前回に引き続き、Webpack を利用して色々トランスパイルしつつ、Browserslist の効果を確認していく企画。前回の記事の続きなので未見の方はソチラを先にドウゾ。

また、最終成果物のコードは以下の GitHub リポジトリで公開しているのでコチラもドウゾ。

今回は SCSS ファイルを CSS ファイルとしてトランスパイルして出力しつつ、そこに Browserslist を参照しての Autoprefixer を効かせてみる。

目次

仕組みを理解する

最初は手前の一文を理解するのが難しいと思う (自分も理解が追いつかなかった) ので、仕組みを整理する。

SCSS ファイルはそのままではブラウザで認識されない。生の CSS にトランスパイルする必要がある。ココまでは良いだろう。

今回、Browserslist の設定を利用して、新しめのコードにはベンダプレフィックス等のフォールバックを追加させたいと考えている (Browserslist の効果を知るのが当初の目的だったので…)。

SCSS コードに Browserslist を適用させるには、まず SCSS から CSS へトランスパイルを済ませてしまい、その CSS に対して、Autoprefixer がベンダプレフィックスをブチ込む流れになる。Autoprefixer が Browserslist の設定ファイルを参照して、どの程度ベンダプレフィックスを追加するか決めるというワケ。

CSS コードに Autoprefixer を効かせるためには、PostCSS というパーサが間に入ることになる。

ココまでで SCSS ファイルを CSS コードに変換できたワケだが、その CSS コードを Webpack で処理できるようにするため、css-loader という Loader が必要になる。

css-loader をかませると、CSS コードを ./dist/js/main.js 内にバンドル出来るようになるのだが、今回は JS ファイル内にバンドルするのではなく、.css ファイルとして別に書き出したい。そこでさらに mini-css-extract-plugin という Webpack プラグインをかませる。

ビルド手順と使用ツール

ということで、手順と関連しそうなツールを並べると、次のとおり。

  1. 元の SCSS コードを CSS にトランスパイルする
    • sass-loader という Loader でファイルを読み込む
    • トランスパイル自体は sass パッケージが行う
  2. CSS コードにベンダプレフィックスを追加する
    • PostCSS が間に入り、Autoprefixer が対応する
    • Autoprefixer は Browserslist 設定ファイルを参照する
  3. 生成された CSS コードを Webpack が操作できるようにする
    • css-loader
  4. JS ファイル内にバンドルするのではなく、CSS ファイルとして書き出させる
    • mini-css-extract-plugin

うーむ。何やら色々出てきたな…。Gulp プラグインが大量に必要になるのと同じことが Webpack でも起きている…。

ちなみに、style-loader という似たような名前の Loader もあるのだが、コチラは JS 内に取り込んだ CSS コードを、HTML 中に style 要素で直接流し込むための Loader。SPA でコンポーネント単位にカプセル化する仕組みに利用したりだとか、用途が異なる。

Webpack ちからの養い方

何度か出てきている Loader って何やねん、というと、雑に言えば Loader はファイルを扱うプラグイン的なモノ。英語ながら公式サイトに解説がある。

どうやったらそういう Loader があるの分かんねん、というと、公式サイトに各 Loader の解説があるので、コレを読みながら何となく触る。

必要な Loader・Plugin をインストールする

それでは必要なツール類をインストールしよう。ベースとなるのは前回の記事までで、webpackwebpack-cli がインストール済のプロジェクトとする。

$ npm install -D sass sass-loader autoprefixer postcss-loader css-loader mini-css-extract-plugin
"devDependencies": {
  "autoprefixer": "9.8.5",
  "css-loader": "4.0.0",
  "mini-css-extract-plugin": "0.9.0",
  "postcss-loader": "3.0.0",
  "sass": "1.26.10",
  "sass-loader": "9.0.2",
  
  "webpack": "4.44.0",
  "webpack-cli": "3.3.12"
}

あぁー多いなぁー。依存パッケージが多くて嫌だなぁー。w

素の CSS を扱ってみる

いきなり全ての Loader や Plugin を使うと混乱するので、順に行こう。

まずは最も基礎となる css-loader だけを使って、CSS ファイルを JS ファイル内にバンドルしてみる。

次の2ファイルを書いていく。

import '../css/main.css';
body {
  color: red;
}

ソースコードができたら、ビルド設定を次のように記述する。

const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/js/main.ts',
  output: {
    path    : path.resolve(__dirname, 'dist/'),
    filename: 'js/[name].js'
  },
  // ↓ 以下を追加
  module: {
    rules: [
      {
        test: (/\.css$/u),
        use: [
          'css-loader'
        ]
      }
    ]
  }
};

コレでビルドしてみると、./dist/js/main.js の中に、次のような CSS コードが含まれていることが分かるだろう。

o.push([t.i, "body {\n  color: red;\n}", ""])

なるほど、css-loader を使うと、CSS ファイルを読み込んで JS にバンドルできた。

SCSS を変換してみる

次は CSS ではなく SCSS ファイルを変換してみる。

import '../scss/main.scss';
body {
  color: red;
  
  p {
    color: blue;
  }
}
// .sass・.scss・.css ファイルを対象にする
{
  test: (/\.(sa|sc|c)ss$/u),
  use: [
    'css-loader',
    'sass-loader'
  ]
}

ココで重要なのは、use 配列の順番。Webpack のルールは配列の最後に書いた Loader・Plugin から順に適用される。よって、配列の並びとしては css-loader が先に来ているが、実際に適用されるのは sass-loder が最初で、次に css-loader となる。

このように設定してビルドすると、./dist/js/main.js の中に、次のような文字列を確認できると思う。

o.push([t.i, "body{color:red}body p{color:blue}", ""])

body p {} の指定などを見ると、sass-loader によって SCSS をトランスパイルできていることが分かる。

CSS ファイルを別に出力する

トランスパイルができたので、CSS ファイルを別に書き出すところをやってみる。

ソースコードはそのままに、ビルド設定を書き換える。

const path = require('path');

// ↓ 追加
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  mode: 'production',
  entry: './src/js/main.ts',
  output: {
    path    : path.resolve(__dirname, 'dist/'),
    filename: 'js/[name].js'
  },
  // ↓ plugins セクションを追加
  plugins: [
    new MiniCssExtractPlugin({
      filename: '/css/[name].css'
    })
  ],
  module: {
    rules: [
      {
        test: (/\.(sa|sc|c)ss$/u),
        use: [
          MiniCssExtractPlugin.loader,  // ← ココに追加
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  }
};

use 配列の最初に mini-css-extract-plugin を配置したので、処理順としては最後に適用されることになる。つまり、SCSS を sass-loader がトランスパイル、それを JS で扱えるよう css-loader が加工するが、その結果を別ファイルに書き出すということで mini-css-extract-plugin が働く、という順番だ。

mini-css-extract-pluginplugins セクションで、出力ファイルのパスを設定している。

コレでビルドしてみると、./dist/js/main.js の中から CSS コードの記述が消え、代わりに ./dist/css/main.css が出力されていることが分かるだろう。この中身は ./src/scss/main.scss がトランスパイルされたモノとなる。

PostCSS + Autoprefixer + Browserslist によるベンダプレフィックス付与を試す

ココまでで SCSS のトランスパイルは完璧に出来るようになった。最後に、Autoprefixer をかませてベンダプレフィックスを付与させてみよう。

Autoprefixer をかませるためのツールとして PostCSS (postcss-loader) が登場し、Autoprefixer がどのベンダプレフィックスを適用すべきかを判断するために、内部で Browserslist を使用する、という関係だ。

.container {
  box-sizing: border-box;
  display: grid;
  grid-gap: 1rem;
  grid-template: "header  header"  1fr
                 "nav     main  "  minmax(100px, 2fr)
                 "nav     footer"  1fr /
                  300px   1fr;
  
  header { grid-area: header; }
  nav    { grid-area: nav   ; }
  main   { grid-area: main  ; }
  footer { grid-area: footer; }
}
> 5%
last 2 versions
Firefox ESR
dead

# IE に対応させる
ie >= 6
// PostCSS 実行時に Autoprefixer を読み込み、Browserslist の指定を利用させる
module.exports = {
  plugins: [
    require('autoprefixer')({
      // CSS Grid に関する対応を入れる (IE 向け)
      grid: true
    })
  ]
};
{
  test: (/\.(sa|sc|c)ss$/u),
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    'postcss-loader',  // ← 追加。Autoprefixer の指定は postcss.config.js で行う
    'sass-loader'
  ]
}

こんな感じ。いざビルドしてみると、./dist/css/main.css にベンダプレフィックスが追加されているのが確認できるはずだ。

.container {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  display: -ms-grid;
  display: grid;
  grid-gap: 1rem;
  -ms-grid-rows: 1fr 1rem minmax(100px, 2fr) 1rem 1fr;
  -ms-grid-columns: 300px 1rem 1fr;
  grid-template: "header  header"1fr "nav     main  "minmax(100px, 2fr) "nav     footer"1fr/300px 1fr
}

スバラシイ。

.browserslistrc の内容をいじくったりすると、ベンダプレフィックスの付き方が変わることが確認できるはずだ。

次回に続く

SCSS のトランスパイルと、Autoprefixer による Browserslist の利用はココまで。

次回は ECMAScript や TypeScript を Babel でトランスパイルしてみる。