Dart Sass (@use
, @forward
使用)で Bootstrap 4 の変数・マップを上書きしたくなったので実験してみました。
今まで (node-sass / LibSass)
前提
/
└ src/
├ html/
│ └ index.html
│
└ scss/
├ assets/
│ └ bootstrap/
│ │ └ bootstrap/
│ │ └ 略
│ └ bootstrap.scss
│
├ foundation/
│ ├ _index.scss
│ ├ _mixin.scss
│ └ _variables.scss
│
├ layout/
│ ├ _l-footer.scss
│ ├ _l-header.scss
│ └ _l-main.scss
│
├ object/
│ ├ component/
│ │ └ 略
│ ├ project/
│ │ └ 略
│ └ utility/
│ └ 略
│
└ index.scss
今回の検証で使用するプロジェクトのディレクトリ構造は、このような状態とします。
src/html/index.html
<div class="mt-4">
<a href="#" class="btn btn-primary m-3">プライマリーボタン</a><!-- プライマリーカラーのボタン -->
<a href="#" class="btn btn-main m-3">メインボタン</a><!-- デフォルトにはないボタン -->
</div>
例として、こんな HTML があったとして。
src/scss/foundation/_scss_variable.scss
$theme-colors: (
/* 上書き */
primary: $own-color,
/* デフォルトにないカラーの追加 */
main: $own-main-color,
);
マップの定義を上書きするコードを書きます。
src/scss/foundation/_index.scss
@import "variables"; //変数(Bootstrap の変数上書きのコードあり)
@import "mixin";
@import "../assets/bootstrap/bootstrap"; //bootstrap
同じ foundation
の中に読み込み用の Scss を用意します。
注意する点としては、「 Bootstrap の Scss を読み込むより前に、 Bootstrap の変数(マップ)を上書きするための自前の定義が書かれた Scss を読み込む」ということ。
src/scss/index.scss
@import "./foundation/index"; //読み込み
最後に、実際に index.css
にコンパイルされる src/scss/index.scss
で src/scss/foundation/_index.scss
を読み込みます。
今までは、これで変数の上書きができました。
Dart Sass での検証1 (失敗 / 単純に @import
を @use
, @forward
に書き換え)
さて、ここで単純に今まで @import
で記述していたのを @use
, @forward
に書き換えてみます。
src/scss/foundation/_scss_variable.scss
$theme-colors: (
/* 上書き */
primary: $own-color,
/* デフォルトにないカラーの追加 */
main: $own-main-color,
);
上書きしたい変数(マップ)の定義はそのまま。
src/scss/foundation/_index.scss
@forward "scss_variables"; //変数(Bootstrap の変数上書きのコードあり)
@forward "mixin";
@forward "../assets/bootstrap/bootstrap"; //bootstrap
今度は @import
ではなく、 @forward
に書き換えます。
src/scss/layout/_l-header.scss
@use "../foundation" as f;
.l-header {
.navbar-brand {
&,
&:link,
&:visited {
color: f.$own-main-color;
}
&:hover,
&:active,
&:focus {
color: f.$own-main-color_l;
}
}
}
// 略
実際に src/scss/foundation/_index.scss
を読み込んで使用する Scss で @use
による読み込みを行います。
src/scss/index.scss
@use "layout/l-header"; //_l-header.scss の中で @use を使って src/scss/foundation/_index.scss を読み込み、使用
@use "layout/l-main";
@use "layout/l-footer";
こんな形に書き換えます。
流れとしては、「src/scss/index.scss
-(@use
)-> src/scss/layout/_l-header.scss
-(@forward
)-> src/scss/foundation/_index.scss
」という関係。
この状態で Dart Sass によるコンパイルを実行すると、$theme-colors
の変数名が重複しているのでエラーになってしまいます。
Error: src/scss/foundation/_index.scss
Error: Two forwarded modules both define a variable named $theme-colors.
╷
3 │ @forward "variables";
│ ━━━━━━━━━━━━━━━━━━━━ original @forward
... │
5 │ @forward "../assets/bootstrap/bootstrap"; //bootstrap
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new @forward
╵
src/scss/foundation/_index.scss 5:1 @use
src/scss/layout/_l-header.scss 3:1 @use
src/scss/index.scss 8:1 root stylesheet
回避するためにはいくつかの方法が考えられますが、 Bootstrap 4 の @import
を書き換えていくのは大変な上に既存ライブラリに手を入れるのはできれば避けたいので今回は不採用。
他の方法としては with
を使う方法が考えられます。
ただし、これもいくつか工夫が必要です。
Dart Sass での検証2 (失敗 / @foward
~ with
)
安直に with
を使おうかと考えましたが、構文的に with
は @use
でしか使えません。
そのため、以下のような書き換えは不可です。
src/scss/foundation/_index.scss
@forward "../assets/bootstrap/bootstrap" with (
/* with の中では use で読み込んだファイルのスコープは使えない */
$theme-colors: (
/* 上書き */
primary: #333,
/* デフォルトにないカラーの追加 */
main: red,
),
); //bootstrap
Dart Sass での検証3 (失敗 / ファイルスコープ)
@use
とセットで使うということが分かったので、今度は @use
で読み込んでいる場所で with
を付けたそうと思いました。
src/scss/layout/_l-header.scss
@use "../foundation" as f with (
$theme-colors: (
/* 上書き */
primary: f.$own-color,
/* デフォルトにないカラーの追加 */
main: f.$own-main-color,
),
);
.l-header {
.navbar-brand {
&,
&:link,
&:visited {
color: f.$own-main-color;
}
&:hover,
&:active,
&:focus {
color: f.$own-main-color_l;
}
}
}
// 略
ただし、このような既述をすると以下のエラーが発生します。
Error: src/scss/layout/_l-header.scss
Error: There is no module with the namespace "f".
╷
7 │ primary: f.$own-color,
│ ^^^^^^^^^^^^^
╵
src/scss/layout/_l-header.scss 7:18 @use
src/scss/index.scss 8:1 root stylesheet
with
の中では、その直前で既述した名前空間はまだ使えません。
「独自に定義したメインカラーでプライマリーカラーを上書きしたい」というケースが多いと思われるので、 with
で上書きするときにメインカラーが変数で指定できないのは困ります。
そこで、独自定義の変数読み込みと Bootstrap 4 の読み込み部分を分けることにしました。
最終形
ディレクトリ構造
/
└ src/
├ html/
│ └ index.html
│
└ scss/
├ assets/
│ └ bootstrap/
│ │ └ bootstrap/
│ │ └ 略
│ └ bootstrap.scss
│
├ foundation/
│ ├ _index.scss
│ ├ _mixin.scss
│ └ _variables.scss
│
├ global/
│ └ _index.scss # 追加
│
├ layout/
│ ├ _l-footer.scss
│ ├ _l-header.scss
│ └ _l-main.scss
│
├ object/
│ ├ component/
│ │ └ 略
│ ├ project/
│ │ └ 略
│ └ utility/
│ └ 略
│
└ index.scss
src/scss/foundation/_scss_variable.scss
先程まであった上書き用のコードは削除。
src/scss/foundation/_index.scss
@forward "variables";
@forward "mixin";
Bootstrap 4 を読み込む @forward
を削除。
src/scss/global/_index.scss
@forward "../assets/bootstrap/bootstrap"; //bootstrap
Bootstrap 4 を読み込む部分を切り出したのを、 src/scss/global/_index.scss
とします。
src/scss/layout/_l-header.scss
@use "../foundation" as f;
@use "../global" as g with (
$theme-colors: (
/* 上書き */
primary: f.$own-color,
/* デフォルトにないカラーの追加 */
main: f.$own-main-color,
),
);
.l-header {
.navbar-brand {
&,
&:link,
&:visited {
color: f.$own-main-color;
}
&:hover,
&:active,
&:focus {
color: f.$own-main-color_l;
}
}
}
// 略
実際に使う部分。変数は foundation
, Bootstrap 4 は global
で読み込み、 foundation
を先に読み込むことで、 with
の中で使用できるようにしました。
src/scss/index.scss
@use "layout/l-header";
@use "layout/l-main";
@use "layout/l-footer";
index.css
になる部分は読み込みだけ。
この形にすることでようやく当初意図していた形にすることができました。
備考1
今回の方法では Bootstrap 4 の中身は一切触れない方向で実装しました。そのため、 Bootstrap 4 の中は以前として @import
で読み込まれており、変数はグローバルスコープに定義されるようです。
そのため、今回は src/scss/layout/_l-header.scss
でしか with
を使用していませんが……
デフォルト状態ではこのような状態になります(プライマリーカラーがデフォルト、右側はデフォルト状態では存在しない btn-main
クラスが付与されているため背景色なし)。
src/scss/layout/_l-header.scss
に with
を適用しただけで、メイン部分のボタンも影響を受けます。今回の場合は「全体で色を変更したい」のでこれで良いのですが、 @use
や @forward
の位置付けからすると役割を発揮できていない状態なので微妙なところ。
今後、 Bootstrap が @use
, @forward
に変更した場合にこの辺りの挙動は変わると思われます。
※ちなみに、v5.0.0-alpha3 でもまだ @import
でした。
備考2
備考1と関連しますが、グローバルスコープに定義されるということは……
src/scss/layout/_l-header.scss
@use "../foundation" as f;
@use "../global" as g;
.l-header {
.navbar-brand {
&,
&:link,
&:visited {
color: f.$own-main-color;
}
&:hover,
&:active,
&:focus {
color: f.$own-main-color_l;
}
}
}
// 略
src/scss/layout/_l-header.scss
は普通に src/scss/global/_index.scss
を読み込み……
src/scss/layout/_l-main.scss
@use "../foundation" as f;
@use "../global" as g with (
$theme-colors: (
/* 上書き */
primary: f.$own-color,
/* デフォルトにないカラーの追加 */
main: f.$own-main-color,
),
);
.l-main {
background-color: f.$own-bg-color;
color: f.$own-color;
.btn-main {
color: f.$own-color;
}
}
// 略
src/scss/layout/_l-header.scss
の後に読み込まれる src/scss/layout/_l-main.scss
で with
を使用した場合、以下のエラーが発生します。一度読み込んだモジュールが with
使用で再度読み込まれている、という旨のエラーですね。
Error: This module was already loaded, so it can't be configured using "with".
┌──> src/scss/layout/_l-main.scss
4 │ ┌ @use "../global" as g with (
5 │ │ $theme-colors: (
6 │ │ /* 上書き */
7 │ │ primary: f.$own-color,
8 │ │ /* デフォルトにないカラーの追加 */
9 │ │ main: f.$own-main-color,
10│ │ ),
11│ │ );
│ └─^ new load
╵
┌──> src/scss/layout/_l-header.scss
4 │ @use "../global" as g;
│ ━━━━━━━━━━━━━━━━━━━━━ original load
╵
src/scss/layout/_l-main.scss 4:1 @use
src/scss/index.scss 9:1 root stylesheet
Error: src/scss/layout/_l-main.scss
Error: This module was already loaded, so it can't be configured using "with".
┌──> src/scss/layout/_l-main.scss
4 │ ┌ @use "../global" as g with (
5 │ │ $theme-colors: (
6 │ │ /* 上書き */
7 │ │ primary: f.$own-color,
8 │ │ /* デフォルトにないカラーの追加 */
9 │ │ main: f.$own-main-color,
10│ │ ),
11│ │ );
│ └─^ new load
╵
┌──> src/scss/layout/_l-header.scss
4 │ @use "../global" as g;
│ ━━━━━━━━━━━━━━━━━━━━━ original load
╵
src/scss/layout/_l-main.scss 4:1 @use
src/scss/index.scss 9:1 root stylesheet
そのため、 with
を使用するならば全体を通して src/scss/global/_index.scss
が最初に @use
で読み込まれる部分に既述する必要があります。
以上、知らないと全体的に嵌まり所が多い感じだったのでメモしておきます。
参考
with
- Sassの新しいモジュールシステム | BOEL Inc. | ブランディング&デザインファーム
- Sassを@importから@useに置き換えるための手引き | Web Design KOJIKA17