LCL Engineers' Blog

バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログです。

CSSのコンパイラをsass-railsからnode-sassへ変更しました

フロントエンドエンジニアの岡田です。 先日、LCLが運営する夜行バス比較なびのCSSコンパイラを、sass-railsからnode-sassへ変更しました。 今回は、Node.jsへの移行にあたってRailsとの連携をどうしたか、移行で起きた問題などについてまとめました。

node-sassとは

node-sassは、Node.jsでLibSassを動かすためのライブラリです。 LibSassはC++で書かれたSassのコンパイラで、特徴は動作が早いことです。 github.com

sass-railsからnode-sassへ変更した理由

sass-railsのままでも、今のところ大きな問題は起こっていませんでしたが、以下の理由で変更しました。

Sassをフロントエンドの管理下に置きたかった

以前、Reactを導入した際に/app/assets/ から/frontend/ というディレクトリにJSを移しました。 techblog.lclco.com

CSSに関しても/frontend/に移動したほうが、ディレクトリの移動が少なくてすみますし、ReactやNode.jsのパッケージを入れたときにも管理がしやすくなると考えました。

また、フロントエンドエンジニアはRailsよりもNode.jsの方が詳しいというのも大きな理由です。

LibSassにするとコンパイル速度が早くなる

sass-railsのままでも問題が出るほどには遅くないのですが、早ければ早いに越したことはないです。

Railsとnode-sassの役割分担

Railsにはフィンガープリントを変えてクライアントのキャッシュを破棄するという、便利な機能があります。具体的には、ファイルの内容が変わると、ファイル名の末尾に以下のような文字列を追加します。また、参照先も自動的に変更します。

style-052abe0991486977040640c2514339df467276d91bf203fde6ad27f58ecca00e.css

この機能をNode.jsで実装することは難しいため、以下のように分担することにしました。

  • sassをコンパイルしてファイルを1つに結合するところまではnode-sassで行う
  • その後はRailsのアセットパイプラインを使う

なお、今回は試していませんが、webpack-manifest-pluginを使うと、アセットパイプラインを使わずにフィンガープリントを変えることができるようです。

今回やらなかったこと

Railsのアセットパイプラインを使うことにしたので、以下の2点はやりませんでした。

ファイルのminifyのNode.jsへの移行

node-sassに、--output-style compressed オプション追加でできます。 しかし追加してしまうと、CSSのソースコードを確認するときに1ステップ増えてしまいました。 今後、脱sprocketsをする事になったら、また検討しようと思います。

AutoprefixerのNode.jsへの移行

autoprefixer-railsをNode.jsのAutoprefixerへ変えることも検討しましたが、今回は導入を見送りました。Autoprefixerを入れるには、PostCSSを使う必要があるようでした。

node-sass化に伴う変更点

Node.js

package.json

node-sassは、タスクランナー等は使わずに、npm-scriptsで動かしています。 また、node-sassで変換したファイルは、Rails管理下の/app/assets/stylesheets/distへ書き出す設定にしています。

  "dependencies": {
    "node-sass": "^4.9.0",
    "node-sass-globbing": "0.0.23",
・
・
・
  },

  "devDependencies": {
    "onchange": "^4.0.0",
・
・
・
  },
  "scripts": {
・
・
・
    "build": "webpack --config webpack-production.config.js && npm run sass:build",
    "sass:build": "node-sass ./src/sass/entry --importer node_modules/node-sass-globbing/index.js --output ../app/assets/stylesheets/dist --source-comments true",
    "sass:watch": "onchange './src/sass' -- npm run sass:build"
  },

node-sassの他に使ったパッケージ

今回は、以下のパッケージを使いました。

node-sass-globbingは、sassのインポート(@import "dir/*.sass")でglobシンタックスを使用できるようにするパッケージです。 www.npmjs.com

onchangeは、ファイルに変更があったときに処理を実行するパッケージです。 www.npmjs.com node-sassのwatchがうまく動かなかったので使いました。 すでにprettierと一緒に導入済みです。 techblog.lclco.com

SASS(SCSS)

node-sassへの移行では、SCSS自体の記述の変更は必要ありませんでした。 ただ、今回の変更でファイルを1つにまとめるところまでをNode.jsで行うことにしたので、エントリーポイントとなるファイルをNode.js側に新設しました。

/frontend/src/sass/entry/pc/application.scss

+@charset "UTF-8";
+@import "../../lib/**";
+@import "../../components/pc/**";
+@import "../../pc/**";

Rails

アセットパイプライン

以前ブログでご紹介したとおり、Railsのprecompileの前に、npm run buildが実行されるようになっています。 そのため、今回はビルドに関する変更はありませんでした。 techblog.lclco.com ※「Precompileへwebpackビルドのフックを追加」の章

マニフェストファイル(application.scss)

複数のファイルを1つにまとめるところまではNode.jsで行うことにしたので、Railsのマニフェストファイルでは、distに書き出された1つのファイルを読み込むように変更しました。

/app/assets/stylesheets/pc/application.scss

/*
 *= require ../../dist/pc/application.css
 */

移行の際につまったところ

watchが効かない

node-sassにはファイルの変更を検知して動作するwatchオプションがあります。

    -w, --watch                Watch a directory or file

引用:node-sass - npm

しかし、application.scssでディレクトリ指定をしている階層のファイルを変更した場合に、変更を検知してくれませんでした。 そのため、今回はonchangeパッケージを使いました。

package.json

    "sass:watch": "onchange './src/sass' -- npm run sass:build"

watchがうまく動かなかった原因は、私のsassの書き方にあるのかもしれません。 時間を見つけて確認したいと思います。

初回のデプロイに失敗する

LCLでは、Githubへプッシュしたときに、自動的にテスト環境が作られる仕組みになっています。 techblog.lclco.com

今回の変更で、初回のデプロイの時だけCSSが当たらなくなりました。2回目以降のデプロイでは、問題なくCSSが反映されました。

原因は、Railsのマニフェストファイルapp/assets/stylesheets/*/application.scssをGitの管理から外した事でした。

.gitignore

app/assets/stylesheets/*

最初は、node-sassで変換したファイルを上記ディレクトリへ書き出す設定にしていたので、ディレクトリごとGitの管理から外していました。 しかしこれでは、Railsのアセットパイプラインで必要なタイミングに、ファイルが作られないようです。初回のビルド時は、マニフェストファイルがない状態になってしまっていました。

そのため、node-sassで書き出したファイルをRailsのマニフェストファイルとは別のディレクトリへ保存するようにしました。

app/assets/stylesheets/dist/*

マニフェストファイル(application.scss)の章でもお伝えしたように、マニフェストファイルからはdist内のファイルを参照するだけの設定にしています。

ローカルでは実行できるスクリプトがサーバーでは動かなかった

npm scriptsのbulidですが、最初は以下のように並列処理にしていました。

package.json

    "build": "webpack --config webpack-production.config.js & npm run sass:build",

この設定で、ローカルでは問題なく動いていたのですが、テストサーバーでは動きませんでした。 しかも、今回変更したsass部分ではなく、webpackでbuildしているJavaScriptが生成されなくなっていました。

以下のように直列で実行されるように変更したら、無事に動きました。 package.json

    "build": "webpack --config webpack-production.config.js && npm run sass:build",

シェルスクリプトは&で接続することで並列処理、&&では直列処理を行うことができます。 引用:Node.jsユーザーなら押さえておきたいnpm-scriptsのタスク実行方法まとめ - ICS MEDIA

ここは直列処理にしないと、失敗してしまうようです。

まとめ

いろいろな所に詰まって時間がかかってしまいましたが、無事移行できました。 ただ、残念ながらコンパイル速度は、それほど変わっていない印象です。 onchangeを使ったことで、css変更のたびに全ファイルを書き換えてしまっていることも原因の一つかもしれません。 原因の特定を含めて、改善していきたいと思います。