ASP.NET Core でのパッケージ管理と Bootstrap の SCSS ファイルを用いたスタイルの変更

Bootstrap 利用時のフォント指定(その2)で、CSS のカスケーディングの優先順位である「より後から読み込まれたスタイルが優先」を活用して、site.css で設定する方法を書きましたが、その他の項目を変更することもあるでしょうから、「Bootstrap 利用時のフォント指定」と同じように、ASP.NET Core MVC で Bootstrap のスタイルに手を入れる方法の記事もあったほうがいいなと思って書き始めたんですが、ASP.NET Core ではフロントエンドのパッケージ管理方法が大きく変わっているので、そこらへんのことも書かないとなぁということで、備忘録の意味も込めてパッケージ管理がどうなっているのかも含めて書いてみました。なお、「Bootstrap 利用時のフォント指定」では、LESS ファイルを扱いましたが、こちらでは SCSS ファイルを扱ってみます。

まずは ASP.NET Core MVC のプロジェクトを作成します。フレームワークは ASP.NET Core と .NET Framework の組み合わせとし、インストール済みテンプレートから「Web」と「ASP.NET Core Web Application(.NET Framework)」を選びます(名前は AspNetCoreBootstrapScss としました)。
次の画面で「Web アプリケーション」を選択します(認証は不要なので認証無しにしています)。

この状態でソリューション エクスプローラーを見てみると、次のようになっています。

ソリューション エクスプローラー
ソリューション エクスプローラー

また、画面上には表示されていませんが、package.json, bower.json, .bowerrc ファイルも生成されています(ソリューション エクスプローラーの「すべてのファイルを表示」ボタンをオンにすると、package.json, bower.json ファイルが表示されるようになります)。

ファイル一覧
エクスプローラーのファイル一覧

エクスプローラーのファイル一覧にある package.json は Node.js のパッケージ マネージャである npm が利用するファイルで、bower.json, .bowerrc は jQuery や bootstrap などのフロントエンド用のパッケージ マネージャ Bower が利用するファイルです。
ソリューション エクスプローラーに表示されている gulpfile.js はタスク自動化のためのビルドツール gulp のタスク設定ファイルです。

これらのファイルが生成されていることから分かるとおり、ASP.NET Core のプロジェクトでは npm, Bower を利用したパッケージ管理と gulp を利用したタスク自動化を利用するようになっています。これまでは、NuGet 一択のパッケージ管理でいろいろと使い勝手の悪いこともありましたが、Web アプリケーションの構築でよく使われているこれらのツールがプロジェクト生成時点で利用可能になっていることから、すっきりとして利便性も向上していると思います(今後 Windows 10 に導入される bash シェルの話もありますし)。

Visual Studio 側でも、package.json, bower.json ファイルの保存時にパッケージのインストールが行われ、「タスク ランナー エクスプローラー」で gulp または Grunt を利用したタスクの実行を行ったり、「ビルド前、ビルド後、消去、プロジェクトを開く」の4種類のタイミングでタスクを自動実行するように設定したりできます。

ここで、コマンド プロンプトを立ち上げて、Node と npm が使えることを確認してみます。

Node と npm の確認
Node と npm の確認

Node と npm のバージョンが表示され、コマンド プロンプトから使えるようになっていることがわかります。

次に gulp がコマンド プロンプトから使えるかを確かめます。

gulp が使えるか確認
gulp が使えるか確認

このように表示され、コマンド プロンプトからは使えません。まぁコマンド プロンプトからコマンド入力をしない人は使えなくても支障はないでしょうが、やっぱりコマンド プロンプトからも使いたいので、npm でグローバルインストールして使えるようにします。

npm install -g gulp

インストールを行ったので、もう一度コマンド プロンプトから使えるか確かめてみます。

gulp が使えるか再度確認
gulp が使えるか再度確認

コマンド プロンプトから使えるようになりましたが、ローカルの gulp のバージョンが古いので、update しておきたいですね。ただ、ローカルの gulp は package.json ファイルでバージョンが指定されているので、このままでは update できません。

{
  "name": "asp.net",
  "version": "0.0.0",
  "private": true,
  "devDependencies": {
    "gulp": "3.8.11",
    "gulp-concat": "2.5.2",
    "gulp-cssmin": "0.1.7",
    "gulp-uglify": "1.2.0",
    "rimraf": "2.2.8"
  }
}

バージョン部分を外部のエディタを用いて手で書き換えて、コマンド プロンプトから npm update してもいいんですが、これをやると、ソリューション エクスプローラーに依存関係の注意アイコンが表示されます。一度プロジェクトを閉じて再度開くとエラー表示は無くなりますが、Visual Studio 外部で npm のパッケージ操作が行われるとうまく同期できないようです。
ソリューション エクスプローラーの「すべてのファイルを表示」ボタンをオンにして package.json ファイルを表示させて、Visual Studio 上で package.json ファイルに記述されている gulp のバージョンを変更し(バージョンの記述はインテリセンスの補完が使えます)、ついでに gulp-concat, gulp-cssmin, gulp-uglify, rimraf のバージョン記述もインテリセンスの補完機能を使って最新にしておきます(バージョン記述部分をダブル コーテーションを含めて削除し、ダブル コーテーションを記述すると利用できる最新バージョンが出てきます)。そして、SCSS をコンパイルするのに必要な gulp-sass パッケージの記述も追加します。

{
  "name": "asp.net",
  "version": "0.0.0",
  "private": true,
  "devDependencies": {
    "gulp": "3.9.1",
    "gulp-concat": "2.6.0",
    "gulp-cssmin": "0.1.7",
    "gulp-uglify": "1.5.3",
    "gulp-sass": "2.3.1",
    "rimraf": "2.5.2"
  }
}

package.json ファイルを保存すると、自動でパッケージの更新が行われます(更新完了後に「インストールされていません」などの表示が出る場合には、もういちど package.json ファイルの保存を行って再度パッケージの更新をしてください)。Visual Studio 上での npm を利用したパッケージ管理は、こちらの方法を使うのが良さそうです。
ちなみに、npm でローカル インストールしたパッケージは、プロジェクトの「node_modules」フォルダに入ります。

次に、Bower を使って bootstrap-sass-official パッケージをインストールしてみます。
ソリューション エクスプローラーの「すべてのファイルを表示」ボタンがオンになっている状態で、bower.json ファイルを開き、bootstrap-sass-official パッケージの記述を追加します(Web サイトでそのまま利用するわけではなく、CSS 生成のために利用する SCSS ファイルのパッケージを Bower で入れるのがいいのか、npm で入れたほうがいいのか、悩むところですが、ここでは Bower の説明も兼ねて Bower で入れます)。

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.6",
    "bootstrap-sass-official": "3.3.6",
    "jquery": "2.2.0",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.6"
  }
}

これも、bower.json ファイルを保存すると、パッケージがインストールされます。
なお、Bower でインストールしたパッケージは、プロジェクトの「wwwroot\lib」フォルダに入るように設定されています(.bowerrc ファイル)。

ここまでで環境設定ができたので、フォント指定の変更の設定に入ります。
プロジェクトに SCSSs フォルダを作成し、bootstrapCustom.scss ファイルを作成します。

// Import libs

@import "bootstrap";

で、次に gulpfile.js に SCSS ファイルのコンパイル用のタスクを設定します。
Bootstrap 用の bootstrapCustom.css と bootstrapCustom.min.css を生成し、site.min.css には bootstrapCustom.css を結合しないようにしています。

/// <binding Clean='clean' />
"use strict";

var gulp = require("gulp"),
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify"),
    sass = require("gulp-sass");

var webroot = "./wwwroot/";

var paths = {
    js: webroot + "js/**/*.js",
    minJs: webroot + "js/**/*.min.js",
    cssPath: webroot + "css",
    css: webroot + "css/**/*.css",
    minCss: webroot + "css/**/*.min.css",
    concatJsDest: webroot + "js/site.min.js",
    concatCssDest: webroot + "css/site.min.css",
    concatBootstrapCssDest: webroot + "css/bootstrapCustom.min.css",
    siteSassPath: "./SCSSs",
    siteSass: "./SCSSs/*.scss",
    bootstrapScssPath: webroot + "lib/bootstrap-sass-official/assets/stylesheets",
    bootstrapCss: webroot + "css/bootstrapCustom.css"
};

gulp.task("clean:js", function (cb) {
    rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {
    rimraf(paths.concatCssDest, cb);
});

gulp.task("clean", ["clean:js", "clean:css"]);

gulp.task("scss", function () {
    gulp.src(paths.siteSass)
        .pipe(sass({ includePaths: [paths.siteSassPath, paths.bootstrapScssPath] })
            .on("error", sass.logError))
        .pipe(gulp.dest(paths.cssPath));
});

gulp.task("min:js", function () {
    return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

gulp.task("min:css", function () {
    return gulp.src([paths.css, "!" + paths.minCss, "!" + paths.bootstrapCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

gulp.task("min:bootstrapCss", function () {
    return gulp.src(paths.bootstrapCss)
        .pipe(concat(paths.concatBootstrapCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

gulp.task("min", ["min:js", "min:css", "min:bootstrapCss"]);

で、作成した scss タスクをタスクランナー エクスプローラーから実行させると、wwwroot\css フォルダに bootstrapCustom.css ファイルが作成されます。
なお、コマンド プロンプトから

gulp scss

とコマンド実行させると、正常に終了せず、

Run `npm rebuild node-sass` to build the binding for your current environment.

と表示されます。ということで、node-sass(gulp-sass が内部で利用している Node.js のパッケージ) をリビルドしてみます。

npm rebuild node-sass
...
 testing binary.
Binary is fine; exiting.

リビルドが完了した後、再度コマンド プロンプトから実行してみると、正常終了し、wwwroot\css フォルダに bootstrapCustom.css ファイルが作成されます(こういった不都合があるから bash シェルの搭載といった話が出てきてるんでしょうね)。

次は、日本語表示用のフォントを設定するための SCSS ファイルの変更に行きたいところですが、ファイル変更の監視・エラー通知・エラー発生時の監視タスクの停止回避用の設定からいきます。

npm で、gulp-plumber, gulp-notify をインストールします。package.json ファイルを修正し、保存します。

{
  "name": "asp.net",
  "version": "0.0.0",
  "private": true,
  "devDependencies": {
    "gulp": "3.9.1",
    "gulp-concat": "2.6.0",
    "gulp-cssmin": "0.1.7",
    "gulp-uglify": "1.5.3",
    "gulp-sass": "2.3.1",
    "gulp-plumber": "1.1.0",
    "gulp-notify": "2.2.0",
    "rimraf": "2.5.2"
  }
}

次に gulpfile.js を変更し、監視用の watch タスクの追加と scss タスクへのエラー発生通知およびエラー発生時に監視タスクが停止するのを回避するための設定の追加を行います(ここらへんの設定は、whiskers さんの「これからはじめるGulp(6):gulp-plumberとgulp-notifyを使ったデスクトップ通知」を参考にさせていただきました)。

/// <binding Clean='clean' />
"use strict";

var gulp = require("gulp"),
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify"),
    sass = require("gulp-sass"),
    plumber = require("gulp-plumber"),
    notify = require("gulp-notify");

var webroot = "./wwwroot/";

var paths = {
    js: webroot + "js/**/*.js",
    minJs: webroot + "js/**/*.min.js",
    cssPath: webroot + "css",
    css: webroot + "css/**/*.css",
    minCss: webroot + "css/**/*.min.css",
    concatJsDest: webroot + "js/site.min.js",
    concatCssDest: webroot + "css/site.min.css",
    concatBootstrapCssDest: webroot + "css/bootstrapCustom.min.css",
    siteSassPath: "./SCSSs",
    siteSass: "./SCSSs/*.scss",
    bootstrapScssPath: webroot + "lib/bootstrap-sass-official/assets/stylesheets",
    bootstrapCss: webroot + "css/bootstrapCustom.css"
};

gulp.task("clean:js", function (cb) {
    rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {
    rimraf(paths.concatCssDest, cb);
});

gulp.task("clean", ["clean:js", "clean:css"]);

gulp.task("scss", function () {
    gulp.src(paths.siteSass)
        .pipe(plumber({
            errorHandler: notify.onError("Error: <%= error.message %>") //<-
        }))
        .pipe(sass({ includePaths: [paths.siteSassPath, paths.bootstrapScssPath] }))
        .pipe(gulp.dest(paths.cssPath));
});

gulp.task("min:js", function () {
    return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

gulp.task("min:css", function () {
    return gulp.src([paths.css, "!" + paths.minCss, "!" + paths.bootstrapCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

gulp.task("min:bootstrapCss", function () {
    return gulp.src(paths.bootstrapCss)
        .pipe(concat(paths.concatBootstrapCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

gulp.task("min", ["min:js", "min:css", "min:bootstrapCss"]);

gulp.task("watch", function () {
    var targets = [
        paths.siteSass,
        paths.bootstrapScssPath + "/**/*.scss",
    ];
    var watcher = gulp.watch(targets, ["scss"]);
    watcher.on("change", function (event) {
        console.log("File " + event.path + " was " + event.type + ", running sass task.");
    });
});

あとは、gulpfile.js を保存し、タスクランナー エクスプローラーかコマンド プロンプトから監視タスクを実行すれば、ファイルを保存した時点で SCSS ファイルのコンパイルが行われ、エラーがあればウィンドウのポップアップとコンソールへの表示で通知されます。

監視を実行しエラーを発生させたところ
監視を実行しエラーを発生させたところ

なお、監視の終了は、タスクランナー エクスプローラーからタスクを実行した場合は、「watch(実行中)」のタブを閉じる、コマンド プロンプトから実行した場合は Cntl + c で終了させることができます。

それでは、最後に、日本語表示用のフォントを設定するための SCSS ファイルの変更です。
wwwroot\lib\bootstrap-sass-official\assets\stylesheets\bootstrap\_variables.scss ファイルを開いて、46行目の「$font-family-sans-serif:」と83行目の「$icon-font-path:」を変更します。

...
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.

$font-family-sans-serif:  "Lucida Grande", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif !default;
$font-family-serif:       Georgia, "Times New Roman", Times, serif !default;
...
//== Iconography
//
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.

//** Load fonts from this directory.

// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../lib/bootstrap/dist/fonts/") !default;
...

監視タスク watch を実行し、上記の変更を行ってファイルを保存すると、自動的に ~/css/bootstrapCustom.css ファイルが生成されます。その後、min タスクを実行すると、~/css/site.min.css と ~/css/bootstrapCustom.min.css ファイルが生成されます。
これで、スタイルシートを「~/css/bootstrapCustom.css」または「~/css/bootstrapCustom.min.css」から読み込むようにすれば、適切な日本語表示用のフォントで表示されるようになります。


2 thoughts on “ASP.NET Core でのパッケージ管理と Bootstrap の SCSS ファイルを用いたスタイルの変更

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です