読者です 読者をやめる 読者になる 読者になる

かえるの井戸端雑記

開発日誌的な記事だったり備忘録だったり。まとめ記事と言うよりは、七転八倒の様子を小説みたいに読んで眺めてもらえればと。

React+StoryBook+AdminLTEでのComponent開発環境(1)

前提

前回までReactを使ってあれこれしていた。

frogwell.hatenablog.jp

するとやっぱりここにcssも入れ込みたくなってくるのが人情というわけで、lessを使うようにしてみる。

何故lessかといえば、僕がpageを作る時によく使うframeworkのAdminLTEのcssがlessで組まれているから。

github.com

ということではじめる。

まず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というのも必要そう。

github.com

github.com

github.com

うん、どうなってるんだ。

そもそもこれらのloaderってのは何者なのか

polaris-bear.lolipop.jp

webpackがcssを扱うのにcss-loaderとstyle-loaderが必要らしい。lessを使うにはlessが、webpackがlessを使うにはless-loaderが必要、と。

Webpackを色々いじってみる2 - unsweets.log

style-loaderとcss-loaderを使うことでCSSJavascript側でimportすると、requireしたCSSをhead内に挿入してくれるようになる。

ああなるほど。完成したcssをheadの中に差し込むもの、と。

css-loaderを利用するとスコープのあるCSSを出力することもできるがここでは割愛。

css-loaderは生成できるcssがもうちょっとお利口になる感じか。

一般的にはCSSJavascriptでロードせずlink要素でロードすることが多いかもしれない。その場合はextract-text-webpack-pluginを利用して、コンパイルされたCSSをファイルとして出力するようにする。

github.com

ほうほう。そういうものもあるのか。

まだよくわからないけれど使い方としてはこういうところが参考になるのかなあ……。

blog.sasaplus1.com

liginc.co.jp

css-loaderでcssを文字列としてJSに埋め込み、style-loaderを使うことでhead内にstyleとして出力されるようになります。

ああそういうことなのね。

loaderという名前だけだと何しているのかわからなかったけど、babel-loaderならtranspilerだし、css-loaderはjsにcssを埋め込み、style-loaderは埋め込んだcssをheadに出力するための機能の組み込み、と。

qiita.com

webpackの大きな特徴である loader の設定をするところです。webpackはjs以外のどんなファイルでも、loaderさえ使えば読み込むことができます。

ほうほう。基本的には何らかのfileを読み込んだときに入れ込む処理という感じなのか。

CSSはjsにいれておくかfileとして外に出すか

さてと。cssはfile別にするのとjsに入れ込んでしまうの、どっちの方がいいのかな……。

qiita.com

ああ、test環境ではheadにいれてしまい、本番環境ではfileとして生成、みたいな扱いもできるのか。

webpack2でテンプレート用の複数ライブラリを1CSSファイルにビルドする | cupOF Interests

はー。file-loaderというのもあるのか。へー。ほー。こん。いつか何かに使えそうだけど今はいい。

それより今はjsにいれこむのとcss fileとすることの差を知りたいのだ。

shigekitakeguchi.github.io

このあたりは使い方の話。

github.com

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の生成のこと?

qiita.com

  • <style>を差し込む形なので、レガシーなCSSの置き換えが目的であったり、SSRを前提としている場合は、style適用に遅延が発生して、画面がチラつくなどの問題が出やすい。
  • 共通のCSSを色々なjavascriptから読み込んでいる場合、ブラウザキャッシュが効かないので非効率になる
  • CSSjavascriptがワンパックなので、それぞれを並列で読み込んだ場合より遅くなりやすい

ほうほう。

ふーん。

じゃあ基本的にはcssを生成する形でいこうか。

ようやく環境構築

ではまず環境構築。commandは以下の通り。

npm install --save-dev style-loader css-loader less-loader extract-text-webpack-plugin

これは普通に成功。

次に設定だけど。どうするかな。

github.com

ここを見ながら埋めていく。というか僕は今から使う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もどこかで考えないとな(気が散っている)。

github.com

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の定義を調べてみた(けどわからなかった)

github.com

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として実行されていくはず。

で、各自の役割は、

qiita.com

これはかんたんで、最初にless-loaderを通して、LESSで書かれたスタイルシートを一般的なCSS記法に変換して、さらにcss-loaderでCSS特有の依存関係の解決や、識別子のローカル化を行い、style-loaderでJSコード内に書き込むという処理をしているわけです。

これらによると、

  1. less-loader => compiles Less to CSS
  2. css-loader => CSS fileの依存関係の解決(@importなど)
  3. 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地点なのじゃ……。