React+StoryBook+AdminLTEでのComponent開発環境(1)
前提
前回までReactを使ってあれこれしていた。
するとやっぱりここにcssも入れ込みたくなってくるのが人情というわけで、lessを使うようにしてみる。
何故lessかといえば、僕がpageを作る時によく使うframeworkのAdminLTEのcssがlessで組まれているから。
ということではじめる。
まずAdminLTEのlessを適当なところに置く
とりあえず/less/
とか作って、そこにAdminLTEの/dist/bootstrap-less
をそのままcopy。
/dist/less
は/less/less
とかになると格好悪いので/less/AdminLTE
として保存。
で、環境構築
といっても何が必要なのさ
なおなんでless使うのかと言えばAdminLTE使うから。あいつについてくるのはlessなんじゃ……。
lessはless-loaderなるものを使えばwebpackにかませしてcompileできそう。
他にもstyle-loaderとかcss-loaderというのも必要そう。
うん、どうなってるんだ。
そもそもこれらのloaderってのは何者なのか
webpackがcssを扱うのにcss-loaderとstyle-loaderが必要らしい。lessを使うにはlessが、webpackがlessを使うにはless-loaderが必要、と。
Webpackを色々いじってみる2 - unsweets.log
style-loaderとcss-loaderを使うことでCSSをJavascript側でimportすると、requireしたCSSをhead内に挿入してくれるようになる。
ああなるほど。完成したcssをheadの中に差し込むもの、と。
css-loaderは生成できるcssがもうちょっとお利口になる感じか。
一般的にはCSSをJavascriptでロードせずlink要素でロードすることが多いかもしれない。その場合はextract-text-webpack-pluginを利用して、コンパイルされたCSSをファイルとして出力するようにする。
ほうほう。そういうものもあるのか。
まだよくわからないけれど使い方としてはこういうところが参考になるのかなあ……。
css-loaderでcssを文字列としてJSに埋め込み、style-loaderを使うことでhead内にstyleとして出力されるようになります。
ああそういうことなのね。
loaderという名前だけだと何しているのかわからなかったけど、babel-loaderならtranspilerだし、css-loaderはjsにcssを埋め込み、style-loaderは埋め込んだcssをheadに出力するための機能の組み込み、と。
webpackの大きな特徴である loader の設定をするところです。webpackはjs以外のどんなファイルでも、loaderさえ使えば読み込むことができます。
ほうほう。基本的には何らかのfileを読み込んだときに入れ込む処理という感じなのか。
CSSはjsにいれておくかfileとして外に出すか
さてと。cssはfile別にするのとjsに入れ込んでしまうの、どっちの方がいいのかな……。
ああ、test環境ではheadにいれてしまい、本番環境ではfileとして生成、みたいな扱いもできるのか。
webpack2でテンプレート用の複数ライブラリを1CSSファイルにビルドする | cupOF Interests
はー。file-loaderというのもあるのか。へー。ほー。こん。いつか何かに使えそうだけど今はいい。
それより今はjsにいれこむのとcss fileとすることの差を知りたいのだ。
このあたりは使い方の話。
It moves all the require(“style.css”)s in entry chunks into a separate single CSS file. So your styles are no longer inlined into the JS bundle, but separate in a CSS bundle file (styles.css). If your total stylesheet volume is big, it will be faster because the CSS bundle is loaded in parallel to the JS bundle.
これは、入力チャンク内のすべてのrequire( “style.css")を別々の単一のCSSファイルに移動します。 したがって、あなたのスタイルはJSバンドルにインライン展開されなくなりますが、CSSバンドルファイル(styles.css)では分離されます。 CSSバンドルがJSバンドルと並行してロードされるため、合計スタイルシートボリュームが大きい場合は、CSSバンドルが高速になります。
本家見ろという話だった。
んー。bundleが高速になるだけ? このbundleってcssの生成のこと?
- <style>を差し込む形なので、レガシーなCSSの置き換えが目的であったり、SSRを前提としている場合は、style適用に遅延が発生して、画面がチラつくなどの問題が出やすい。
- 共通のCSSを色々なjavascriptから読み込んでいる場合、ブラウザキャッシュが効かないので非効率になる
- CSSとjavascriptがワンパックなので、それぞれを並列で読み込んだ場合より遅くなりやすい
ほうほう。
ふーん。
じゃあ基本的にはcssを生成する形でいこうか。
ようやく環境構築
ではまず環境構築。commandは以下の通り。
npm install --save-dev style-loader css-loader less-loader extract-text-webpack-plugin
これは普通に成功。
次に設定だけど。どうするかな。
ここを見ながら埋めていく。というか僕は今から使うlessの構造もどうなっているのかわかってないんだけど。……まあいいか。
とりあえずcopy and paste。で、それはそれとして/lessというのを作ってそれ以下に*.lessを配置しているけど、webpackのresolverはそこを見に行ってくれるんだろうか。
ExtractTextPluginにoptionがあるのかなあ。
const ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", disable: process.env.NODE_ENV === "development" });
ここのfilename かな。どこをrootとしていて、nameとcontenthashをどこから手に入れて入れるのかよくわからないけれど。
ううむ。よくわからない。
ああ。entryとして登録しておけば良さそう。
fileをこう配置。
./less ├── AdminLTE │ ├── 404_500_errors.less │ ├── AdminLTE.less │ ├── alerts.less │ ├── bootstrap-social.less │ ├── boxes.less │ ├── buttons.less │ ├── callout.less │ ├── carousel.less │ ├── control-sidebar.less │ ├── core.less │ ├── direct-chat.less │ ├── dropdown.less │ ├── forms.less │ ├── fullcalendar.less │ ├── header.less │ ├── info-box.less │ ├── invoice.less │ ├── labels.less │ ├── lockscreen.less │ ├── login_and_register.less │ ├── mailbox.less │ ├── miscellaneous.less │ ├── mixins.less │ ├── modal.less │ ├── navs.less │ ├── print.less │ ├── products.less │ ├── profile.less │ ├── progress-bars.less │ ├── select2.less │ ├── sidebar-mini.less │ ├── sidebar.less │ ├── skins │ │ ├── _all-skins.less │ │ ├── skin-black-light.less │ │ ├── skin-black.less │ │ ├── skin-blue-light.less │ │ ├── skin-blue.less │ │ ├── skin-green-light.less │ │ ├── skin-green.less │ │ ├── skin-purple-light.less │ │ ├── skin-purple.less │ │ ├── skin-red-light.less │ │ ├── skin-red.less │ │ ├── skin-yellow-light.less │ │ └── skin-yellow.less │ ├── small-box.less │ ├── social-widgets.less │ ├── table.less │ ├── timeline.less │ ├── topology-list.less │ ├── users-list.less │ ├── variables.less │ └── vario-mixins.less └── bootstrap-less ├── mixins │ ├── alerts.less │ ├── background-variant.less │ ├── border-radius.less │ ├── buttons.less │ ├── center-block.less │ ├── clearfix.less │ ├── forms.less │ ├── gradients.less │ ├── grid-framework.less │ ├── grid.less │ ├── hide-text.less │ ├── image.less │ ├── labels.less │ ├── list-group.less │ ├── nav-divider.less │ ├── nav-vertical-align.less │ ├── opacity.less │ ├── pagination.less │ ├── panels.less │ ├── progress-bar.less │ ├── reset-filter.less │ ├── reset-text.less │ ├── resize.less │ ├── responsive-visibility.less │ ├── size.less │ ├── tab-focus.less │ ├── table-row.less │ ├── text-emphasis.less │ ├── text-overflow.less │ └── vendor-prefixes.less ├── mixins.less └── variables.less
んで、entryをこう修正。
const entry = { test: path.resolve(__dirname, '../src/root', 'test.jsx'), base_style: path.resolve(__dirname, '../less/AdminLTE/skins', 'skin-black.less') };
webpackのconfigを以下のように修正。
const path = require('path'); const entry = require('./entry'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "less-loader" } ], } ] }, plugins: [ extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", disable: false, // disable: process.env.NODE_ENV === "development", allChunks: true }) ] };
はいerror。
ERROR in ./~/css-loader!./~/less-loader/dist!./less/AdminLTE/skins/skin-black.less Module build failed: Error: Cannot find module 'less'
なんでさ。
余計な設定を削る
とりあえずpluginは考えないことにする。なんでいつも最初から全部やろうとするのか。
webpackのconfigを直して、
const path = require('path'); const entry = require('./entry'); module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "less-loader" } ], } ] } };
で、手直しだけど。
あ。これって警告通りで、単にlessいれてなくない?
npm install --save-dev less
を実行。それからもう一度compile。
成功した。馬鹿野郎。
file生成
次はfile生成。つまりextract-text-webpack-pluginを使う。
webpackのconfigをいじって、と。
const path = require('path'); const entry = require('./entry'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "less-loader" } ], } ] }, plugins: [ extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", disable: false, // disable: process.env.NODE_ENV === "development", allChunks: true }) ] };
これでbuildするとbase_style.jsが生成された。
いやそうではなく。cssを生成したいのだよ。
あー。use: extractLess.extract
とかしてるな。じゃあpluginの中で定義しては駄目だ。
こう直す。
const path = require('path'); const entry = require('./entry'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", disable: false, // disable: process.env.NODE_ENV === "development", allChunks: true }) module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: extractLess.extract({ use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "less-loader" } ], }) } ] }, plugins: [ extractLess ] };
はい。error。
これがいくつか出てきた。
Module build failed: ReferenceError: window is not defined
ReferenceError: window is not defined
うーん。とりあえずextractLessの余計なoptionを削ってみる。
const extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", //disable: false, // disable: process.env.NODE_ENV === "development", //allChunks: true });
さっきよりerrorは減ったけど結局同じものが出ている。
ERROR in ./less/AdminLTE/skins/skin-black.less Module build failed: ReferenceError: window is not defined
で、/less/AdminLTE/skins/skin-black.less doesn't export content
とのこと。まあそらなあ……。
sourceMapもどこかで考えないとな(気が散っている)。
I was getting window is not defined because I had style-loader in my list of loaders. When I moved it to the fallbackLoader I stopped having that issue.
え、そうなの?
じゃあ以下に習ってfallbackに定義。
GitHub - webpack-contrib/less-loader: Less loader for webpack. Compiles Less to CSS.
そしてこうなる。
const path = require('path'); const entry = require('./entry'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractLess = new ExtractTextPlugin({ filename: "[name].[contenthash].css", //disable: false, // disable: process.env.NODE_ENV === "development", //allChunks: true }); module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: extractLess.extract({ use: [ { loader: "css-loader" }, { loader: "less-loader" } ], fallback: "style-loader" }) } ] }, plugins: [ extractLess, ], };
で、どうだ。あ、通った。
Hash: 2860d21cce9f97b3191d Version: webpack 2.3.3 Time: 7441ms Asset Size Chunks Chunk Names test.js 2.9 MB 0 [emitted] [big] test skin_black_dark.js 6.57 kB 1 [emitted] skin_black_dark skin_black_dark.332733b683c022f22a6d744517d935c9.css 13.9 kB 1 [emitted] skin_black_dark [0] ./~/process/browser.js 5.3 kB {0} [built] [4] ./~/react/react.js 56 bytes {0} [built] [14] ./~/babel-runtime/helpers/classCallCheck.js 208 bytes {0} [built] [15] ./~/babel-runtime/helpers/createClass.js 904 bytes {0} [built] [18] ./~/babel-runtime/core-js/object/get-prototype-of.js 104 bytes {0} [built] [19] ./~/babel-runtime/helpers/inherits.js 1.11 kB {0} [built] [117] ./~/react-router/es/index.js 637 bytes {0} [built] [185] ./src/pages/routing.jsx 3.04 kB {0} [built] [186] ./src/saga/test/index.js 2.05 kB {0} [built] [194] ./src/root/test.jsx 2.1 kB {0} [built] [196] ./src/saga/test/testtimer.js 1.24 kB {0} [built] [256] ./less/AdminLTE/skins/skin-black.less 41 bytes {1} [built] [404] ./~/css-loader!./~/less-loader/dist!./less/AdminLTE/skins/skin-black.less 14.4 kB [built] [407] ./~/style-loader/addStyles.js 8.51 kB [built] [408] ./~/style-loader/fixUrls.js 3.01 kB [built] + 394 hidden modules Child extract-text-webpack-plugin: [0] ./~/css-loader/lib/css-base.js 2.19 kB {0} [built] [1] ./~/base64-js/index.js 3.48 kB {0} [built] [2] ./~/buffer/index.js 48.6 kB {0} [built] [3] ./~/buffer/~/isarray/index.js 132 bytes {0} [built] [4] ./~/css-loader!./~/less-loader/dist!./less/AdminLTE/skins/skin-black.less 14.4 kB {0} [built] [5] ./~/ieee754/index.js 2.05 kB {0} [built] [6] (webpack)/buildin/global.js 509 bytes {0} [built]
fileも確認。
./dist ├── skin_black.332733b683c022f22a6d744517d935c9.css ├── skin_black.js └── test.js
おー。見事。jsとhashがいらないけど。ここを調整してみよう。
hashの削除
まずは filename: “[name].[contenthash].css”,から[contenthash]を削除。
./dist ├── skin_black_dark.css ├── skin_black_dark.js └── test.js
うむ。まずは一つ。次はjsの削除。
その前に今のfallbackの定義の意味をちょっと調べておくか。
fallbackの定義を調べてみた(けどわからなかった)
loader(e.g ‘style-loader’) that should be used when the CSS is not extracted (i.e. in an additional chunk when allChunks: false)
CSSが抽出されないとき(すなわち、すべてのチャンクが偽のときに追加のチャンク内で)に使用されるべきローダ(例えば、「スタイルローダ」)
えーっと。元々順序としてはless-loader=>css-loader=>style-loaderとして実行されていくはず。
で、各自の役割は、
これはかんたんで、最初にless-loaderを通して、LESSで書かれたスタイルシートを一般的なCSS記法に変換して、さらにcss-loaderでCSS特有の依存関係の解決や、識別子のローカル化を行い、style-loaderでJSコード内に書き込むという処理をしているわけです。
これらによると、
- less-loader => compiles Less to CSS
- css-loader => CSS fileの依存関係の解決(@importなど)
- style-loader => 渡されたCSSの内容をStyleタグの形に変換して、bundleに渡す(埋め込む)
……今のところstyle-loaderいらないんじゃないかな。これ。
あ。それでstyle-loaderの代わりにExtractTextPluginを用いてfileを生成する、と。
それでfallbackはExtractTextPluginの中の設定だけど、要するに例外処理か。
でもshould be used when the CSS is not extracted
の時にstyle-loaderが実行されるってどうして? style-loaderってcssが生成された時にbundleに埋め込むためのものでは?
cssが抽出されなかったときに使うloaderというがそもそもどういうsituationなのかわからないんだよなあ……。
make runの時みたく直接fileが生成されない場合とか?
いやでも設定入れ込んでみると生成されてるなあ。
Hash: 8ed94f94a866bbe01282 Version: webpack 2.3.3 Time: 5878ms Asset Size Chunks Chunk Names test.js 1.44 MB 0 [emitted] [big] test skin_black_dark.js 315 kB 1 [emitted] [big] skin_black_dark skin_black_dark.css 13.7 kB 1 [emitted] skin_black_dark test.js.map 1.69 MB 0 [emitted] test skin_black_dark.js.map 374 kB 1 [emitted] skin_black_dark skin_black_dark.css.map 43.2 kB 1 [emitted] skin_black_dark
どうにも次のとっかかりがつかめないのでとりあえずskinのcssと一緒にskinのjsまで作られてしまうのをなんとかしてみよう。
cssとともに生成されるjsの生成を止めてみる
といっても切り分けはたぶんwebpackのresolverと分けることなんだよな。
GitHub - webpack-contrib/less-loader: Less loader for webpack. Compiles Less to CSS.
ここにあるような感じでless-loaderの中にoptionとしてpathsを定義してentryをいれてあげればいいんだと思う。
駄目だった。
babel-loaderに引っかかってたりしないよなと思ってそちらをcomment outしたけど関係なし。どうもExtractTextPluginで作ったextractLessというinstanceの定義において、今のままだとjsもcssも作ってしまうらしい。
jsを生成するのはstyle-loaderのはず。
これをcomment outしてみる?
いやそれでもstyle_black_dark.jsを生成した。なんでさ。
ためしにExtractTextPlugin({filename: "[name].css", disable:true});
としてみたらjsだけが生成された。あれ、なんで生成されるのさ。
https://webpack.github.io/docs/stylesheets.html
ここら辺みていると、jsとcssが両方生成されるのは仕様っぽいな。
disableにするとcssの生成だけ止められる、と。
仕方ないので/dist以下のfile生成はこれでよしとしよう。
とりあえずできた
const webpack = require('webpack'); const path = require('path'); const entry = require('./entry'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractLess = new ExtractTextPlugin({filename: "[name].css"}); module.exports = { entry: entry, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' }, devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx'] }, module: { rules: [ { test: /\.jsx*$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.less$/, use: extractLess.extract({ use: [ { loader: "css-loader", options: { sourceMap: true } }, { loader: "less-loader", options: { sourceMap: true } } ], fallback: "style-loader" }) } ] }, plugins: [ extractLess, ], };
const path = require('path'); const entry = { test: path.resolve(__dirname, '../src/root', 'test.jsx'), skin_black_dark: path.resolve(__dirname, '../less/', 'skin-black.less') }; module.exports = entry;
こんな感じ。
次回に続く
だがlessを使えるだけではまだ足りぬ。bootstrapとかframeworkが使えるようになってようやくstart地点なのじゃ……。