独学の内容をまとめたものです。誤りがございましたら、ご連絡いただけると幸いです。
リンク
- webpackとBabelの基本を理解する(1) ―webpack編―
- webpackとBabelの基本を理解する(2) ―Babel編―
- webpackとBabelの基本を理解する(3) ―webpackとBabel編―
- webpackとBabelの基本を理解する(4) ―React編―
- webpackとBabelの基本を理解する(5) ―Sass編―(本記事)
概要
この記事の概要
- 目的
- フロントエンドの環境構築に利用されるツールへの理解を深める
- 本記事のゴール
- SassのコードをコンパイルしてHTMLに埋め込む方法を知る
- バンドルされたCSSファイルを別ファイルに切り出す方法を知る
- 対象者
- WEBフロント担当者
- HTML,CSS,JavaScript(es2015含む)の基本的な構文を理解している人
- npmの利用方法を理解している人
- 環境・バージョン
- Windows10
- Node.js(推奨版) 10.15.01
- npm 6.4.1
- webpack 4.29.6
- webpack-cli 3.2.3
- css-loader 2.1.0
- node-sass 4.11.0
- sass-loader 7.1.0
- style-loader 0.23.1
- mini-css-extract-plugin 0.4.5
Sass
Sassそのものの説明は割愛させていただきます。。。
Dreamweaverでは標準でSassコードのコンパイル機能があるそうですね。VSCodeも便利なアドオンがあるようで。
インストール
webpackでJavaScriptとJSON以外のファイルを扱いたい場合は、対応するloaderが必要でしたね。以下4つのパッケージをインストールします。
- style-loader: CSSを<style>タグでHTMLに埋め込む
- css-loader: CSSファイルをJS用モジュールに変換する
- sass-loader: SassファイルをCSSに変換するツールを呼び出す
- node-sass: Sassをコンパイルするためのツール
$ npm install style-loader css-loader sass-loader node-sass --save-dev
CSS・Sassをバンドルする
webpack.config.jsの記述
webpack.config.jsに、CSS・Sassファイルをバンドルする際のルールを追記します。
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { url: false }
},
'sass-loader'
]
}
]
}
};
対象ファイルは、.css, .sass, .scss
です。sass-loader → css-loader → style-loader の順に実行されます。
options.url
CSS内に画像への参照url(image.png)
が含まれる場合、それもバンドルの対象となります。画像ファイルをbase64でエンコードするとかでは無い限り、必要な無いので、options.url
にfalse
を指定して対象外とします。(初期値はtrue)
JSファイルとの接続
webpackは、起点となるJSファイルからimport文を頼りに芋づる式にファイルをつなげていくツール、でしたね。ということは、起点ファイルとCSSファイルの接点を作る必要があります。entryに指定しているJSファイルに、CSSファイルのインポート文を記述します。
import 'file.scss';
CSS In JavaScript
sass-loaderでCSSに変換され、css-loaderでJSモジュールに変換されたデータは、style-loaderによって、<style>
タグでHTMLのhead内に埋め込まれます。
正確には、バンドル後のJSファイルにstyle情報とstyleタグを埋め込む処理が追加されます。その為、JSファイルを読み込んだだけでCSSファイルの情報も反映されるようになります。
css-loader | webpack
style-loader | webpack
sass-loader | webpack
CSSファイルを分離する
Plugins
開発中はstyleタグで埋め込みでも良いが、最終的にはCSSファイルは別にしたいという場合も多いと思います。その場合は専用のプラグインを利用します。
これまでに登場したLoaderは、特定のタイプのデータをJSのモジュールに変換する役目を果たします。それ以外のことをやりたい場合は、プラグイン(Plugins)を使って実現します。
MiniCssExtractPluginのインストール
「MiniCssExtractPlugin」を使って分離手術を実施したいと思います。
$ npm install mini-css-extract-plugin --save-dev
MiniCssExtractPlugin | webpack
webpack.config.jsの設定
webpack.config.jsの記述例はこちら。
# ディレクトリ構成
sample/
├ src/
│ ├ scss/
│ │ ├ base.scss
│ │ └ index.scss
│ └ index.js
├ dist/
└ webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'js/sample.js' // /dist/js/sample.jsに出力
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader, // style-loaderの代わり
{
loader: 'css-loader',
options: { url: false }
},
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/sample.css', // /dist/css/sample.cssに出力
})
]
};
冒頭でmini-css-extract-plugin
をインポートしています。webpack.config.jsはNode.jsのファイルなのでrequire
文です。
style-loader
の代わりにMiniCssExtractPlugin.loader
を利用するようにしています。
plugins
loaderで捕捉したCSSモジュールを別ファイルに切り出すため、インポートしたプラグインのインスタンスをplugins
に設定します。
コンストラクタに出力したいファイル名をfilename
で渡します。出力先フォルダはoutput.path
で指定しているフォルダです。ここでは、/dist
の配下に/js
と/css
でそれぞれフォルダが分かれて出力されるようにしています。
SCSSファイルをコンパイルして出力する
フォルダとファイルの準備
上記のディレクトリ構成で、JSファイルとSCSSファイルを準備します。
JSの起点ファイルには、Sass側の起点ファイルとの接点を設けます。
/**
* /src/index.js
*/
import './scss/index.scss';
適当にサスサスしたコードを用意します。CSSファイルで納品なんて場合は、出力後の見え方も意識しておいた方が良いかもしれませんね。Sass側のimportは、webpackではなくnode-sassが対応してくれます。
/**
* /scss/base.scss
*/
html {
font-size: 62.5%;
}
body {
font-family: Roboto, 'メイリオ', Arial, sans-serif;
font-size: 1.4rem;
line-height: 1.3;
color: #333;
background-color: #fdf7eb;
}
a{
-webkit-tap-highlight-color: rgba(0,0,0,0);
&:link, &:hover, &:visited, &:active {
text-decoration: none;
color: inherit;
}
}
@mixin mediaQuery {
@media screen and(max-width: 359px){
@content;
}
}
/**
* /scss/index.scss
*/
@import 'base';
%spreadsheet {
background: url(/img/sprite.png) no-repeat left top;
background-size: 200px auto;
overflow: hidden;
text-indent: -9999px;
}
%BlockContainer {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
%BlockSize {
width: 50%;
height: 60%;
@include mediaQuery {
width: 70%;
height: 80%;
}
}
.areaA {
@extend %BlockContainer;
background-color: #b8d684;
&__card {
@extend %BlockSize;
background-color: #527516;
}
}
.areaB {
@extend %BlockContainer;
background-color: #c8b7cc;
&__card {
@extend %BlockSize;
background-color: #661675;
}
}
.icon {
@extend %spreadsheet;
width: 2rem;
height: 2rem;
$grid:
'0 0',
'0 -60px',
'0 -120px',
'-60px 0',
'-60px -60px',
'-60px -120px'
;
@each $value in $grid {
$i: index($grid, $value);
&--#{$i} {
background-position: #{$value};
}
}
}
.iconB {
@extend %spreadsheet;
width: 6rem;
height: 4rem;
background-position: 0 -80px ;
}
実行結果
webpackを実行した結果はこちら。二つのファイルが結合され、CSSコードに変換されていますね。webpackをwatchモードにした場合、Sassファイルも監視対象に含まれます。
@charset "UTF-8";
html {
font-size: 62.5%; }
body {
font-family: Roboto, 'メイリオ', Arial, sans-serif;
font-size: 1.4rem;
line-height: 1.3;
color: #333;
background-color: #fdf7eb; }
a {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
a:link, a:hover, a:visited, a:active {
text-decoration: none;
color: inherit; }
.iconA, .iconB {
background: url(/img/sprite.png) no-repeat left top;
background-size: 200px auto;
overflow: hidden;
text-indent: -9999px; }
.areaA, .areaB {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center; }
.areaA__card, .areaB__card {
width: 50%;
height: 60%; }
@media screen and (max-width: 359px) {
.areaA__card, .areaB__card {
width: 70%;
height: 80%; } }
.areaA {
background-color: #b8d684; }
.areaA__card {
background-color: #527516; }
.areaB {
background-color: #c8b7cc; }
.areaB__card {
background-color: #661675; }
.iconA {
width: 2rem;
height: 2rem; }
.iconA--1 {
background-position: 0 0; }
.iconA--2 {
background-position: 0 -60px; }
.iconA--3 {
background-position: 0 -120px; }
.iconA--4 {
background-position: -60px 0; }
.iconA--5 {
background-position: -60px -60px; }
.iconA--6 {
background-position: -60px -120px; }
.iconB {
width: 6rem;
height: 4rem;
background-position: 0 -80px; }
日本語
以前に用意した下記構成では、日本語はエンコードされていました。引用符でくくられた文字列はエスケープする仕様でした。エスケープは、css-loaderが担っているようです。
// 各パッケージのバージョン
{
"devDependencies": {
"css-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.4.5",
"node-sass": "^4.10.0",
"sass-loader": "^7.1.0",
"webpack": "^4.16.3"
}
}
// コンパイル前
font-family: Roboto, 'メイリオ', Arial, sans-serif;
// コンパイル後
font-family: Roboto, '\u30e1\u30a4\u30ea\u30aa', Arial, sans-serif;
この構成では日本語がそのまま出力されていました。css-loaderがメジャーアップデートしています。仕様が変わったみたいですね。
// 各パッケージのバージョン
{
"devDependencies": {
"css-loader": "^2.1.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"webpack": "^4.29.1"
}
}
CSS Loader の文字列エスケープがなんかちょっとおかしい件
ファイルの圧縮
webpackのモードをproductionにしていても、切り出されたCSSファイルは圧縮されません。
以前は、css-loaderのオプションにminimize
というのがありましたが、最新版では無くなりました。webpack5では、CSSの圧縮が標準装備されるらしいです。
webpack4でCSSの圧縮を行うには、プラグインを利用します。これがまた結構ややこしそうなので、また別の機会に…。下記のサイトが詳しいです…。
webpack@4 でCSSを抽出する際は mini-css-extract-plugin を使う
Minimizing For Production
近いうちにちゃんと噛み砕いて自分の言葉でまとめたいです。でもその前にwebpack5の安定版が出るかな。どうでしょう。
2019年10月22日追記
sass-loaderの8月の更新(v7.3.0)にて、productionモードの際に圧縮されるようになりました!
あくまでsass-loaderの方なので、css-loaderだけ利用する場合は引き続きプラグインが必要そうです。
参考情報
Sass(SCSS)でCSSコーディングを効率化・メリットと使い方を知る
webpackでCSSやSASSを使う
Adobe Dreamweaver CCを使って爆速でSASS(SCSS)環境を手に入れる
VSCode(Visual Studio Code)で簡単にSASS/SCSSファイルのコンパイルができる拡張機能「Easy Sass」がお勧め
node-sassでSassファイルをコンパイルする