こんにちは、はなくらです
今回は Webpackerのsass-loaderからRailsの app/assets
以下のリソースを利用する という闇を生成する話をします
Q. なんでそんな不毛な事してるの?🙃
A.app/assets/stylesheets
に定義してある変数とコンポーネントをWebpacker上から使いたかったんだ
image-url()
などのないsassを読むだけなら相対パスでimportを頑張ればよいのですが、app/assets
以下のsassでは
image-url
や font-url
という少し厄介なsass関数がいます
これらの関数が使われているsassファイルをSprockets / Webpackerのどちらからimportされても動くようにするというのが今回のぽはなしです
✋ 手順
-
node-sass
上でimage-url
/font-url
関数を使えるようにする - Sprocketsによるコンパイル時と同じパスでリソースを解決できるようにする
- file-loaderでリソースを読み込めるようにする
🔨 解説
1. node-sass
上でimage-url
/font-url
関数を使えるようにする
node-sass
上で読み込まれたsassにはimage-url
や font-url
などの関数がありませんが、node-sassの機能で、JSの関数をsassの世界へ公開する事ができます。
今回はnode-sass
用のimage-url
/ font-url
などの関数を予め定義してあり、パス解決を自前で行うことが出来るnode-sass-asset-functions
というモジュールを利用します。
const assetFunctions = require('node-sass-asset-functions')
const assetFuncs = assetFunctions();
module.exports = {
test: /\.(scss|sass|css)$/i,
use: [
// ...
{ loader: 'sass-loader', options: {
// 生成した関数をnode-sassへパスする
functions: assetFuncs,
} }
// ...
]
}
まずこれでimage-url
などの関数が使えるようになります。
2. Sprocketsによるコンパイル時と同じパスでリソースを解決できるようにする
node-sass-asset-functions
は第1引数にオプションを渡すことができ、このオプションのasset_cache_buster
プロパティにパス解決を行う関数を渡すことで、url(path)
に埋め込まれるpathを自由に書き換えることが出来ます。
この関数内でリソースをファイルシステム上の絶対パスへ変換します。
(相対パスでもいいかもしれないんだけど、どこが基点パスになるのか考えるのが面倒)
const assetFuncs = assetFunctions({
asset_cache_buster(filePath, realPath, done) {
// sass上で `image-url('path/to/image.png')` されると
// filePathに `images/path/to/image.png` のようなパスが入ってくるので、ファイルシステム上の絶対パスへ解決する
const { pathname } = url.parse(filePath);
done({ path: path.join(assetsPath, pathname) });
},
});
さらに、app/assets/stylesheets
内のスタイルシートをimport出来るようにnode-sassへincludePaths
オプションを渡します。
const stylesheetsPath = join(process.cwd(), 'app/assets/stylesheets')
// ...
module.exports = {
test: /\.(scss|sass|css)$/i,
use: [
// ...
{ loader: 'sass-loader', options: {
// `app/assets/stylesheets` へのパスを渡す
includePaths: [stylesheetsPath],
functions: assetFuncs,
} }
// ...
]
}
これでリソースとスタイルシートのパスがSprockets上でのコンパイルと変わらずに解決されるようになります。
3. file-loaderでリソースを読み込めるようにする
最後にfile-loader
を設定することでwebpack上からapp/assets
以下のリソース郡を読み込めるようになります。
image-urlなどをファイルシステムの絶対パスに変換されたものが、file-loaderで読み込まれ、webpackでバンドルされます。
const path = require('path');
module.exports = {
test: /\.(jpg|jpeg|png|gif|svg|eot|ttf|woff|woff2)$/i,
use: [{
loader: 'file-loader',
options: {
// webpacker.yml のentryと同じにする。 publicPathは/で囲わないと、urlの結合がおかしくなるので注意
// ( `url('packsapp/assets/...')` みたいになるし、相対パスだと実行時にちゃんと解決されるかわからん)
publicPath: '/packs/',
name: process.env.NODE_ENV === 'production' ? '[path][name]-[hash].[ext]' : '[path][name].[ext]',
},
}],
};
最終型✨
あとはお好みでExtractTextPluginとかをどうぞ!
const assetFunctions = require('node-sass-asset-functions')
const path = require('path')
const url = require('url')
const assetsPath = path.join(process.cwd(), 'app/assets')
const stylesheetsPath = path.join(assetsPath, 'stylesheets')
const assetFuncs = assetFunctions({
asset_cache_buster: (filePath, realPath, done) => {
const { pathname } = url.parse(filePath);
done({ path: path.join(assetsPath, pathname) });
}
});
module.exports = {
test: /\.(scss|sass|css)$/i,
use: [
{
loader: 'css-loader',
options: { modules: true }
},
{ loader: 'resolve-url-loader' },
{
loader: 'sass-loader',
options: {
includePaths: [stylesheetsPath],
functions: assetFuncs,
}
}
]
}
古い資産を限界まで使い倒したい限界オタクにぽすすめです