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

かえるの井戸端雑記

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

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

前回

frogwell.hatenablog.jp

  • Lessを使えるようになった!

今日のお題

Componentを大分気軽に組めるようになったとは言え、普通web page組むならそれなりのframeworkを使うところ。僕がよく使うのはAdminLTEとBootstrap。

これを普通にcomponentとして取り込めるならそれはそれでいいんだけど、これらをうまく溶け込ませる気がしない。

本当にCSSだけの話で言うならLessで全部取り込めばいいんだろうけど操作するためのあれこれを考えるとjsもという話になって面倒。

で、どうしようかとぐるぐる考え回ったという話。

結論としては「真面目に入れ込むと面倒だからもう3rd partyはどうしてもcomponentからaccess使いたいのでなければMakefileで別途いれこんでやる」という雑な話になった。(Lessを使えるようにしたというのにお前は……)

ではAdminLTEのLessを取り込む

普通にwebpackで生成するだけ。

ERROR in ./~/css-loader?{"sourceMap":true}!./~/less-loader/dist?{"sourceMap":true}!./~/admin-lte/build/less/AdminLTE.less
Module not found: Error: Can't resolve '../img/boxed-bg.jpg' in '/XXXX/node_modules/admin-lte/build/less'
 @ ./~/css-loader?{"sourceMap":true}!./~/less-loader/dist?{"sourceMap":true}!./~/admin-lte/build/less/AdminLTE.less 6:1013-1043
 @ ./~/admin-lte/build/less/AdminLTE.less

んー。variables.lessの“@boxed-layout-bg-image-path: “../img/boxed-bg.jpg”;か。確かに配置が違う。生成後は正しいんだけど。imgがあるのは/dist以下で/build/less/`以下ではない。 まあ仕方ないのでこれだけはfileをcopyしよう。

んでもってwebpackのconfigでfile-loaderにjpgをいれこめるように修正。

      {
        test: /\.(jpg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
      }

できた。

./dist
├── AdminLTE.css
├── AdminLTE.js
├── bootstrap.css
├── bootstrap.js
├── boxed-bg.jpg
├── glyphicons-halflings-regular.eot
├── glyphicons-halflings-regular.svg
├── glyphicons-halflings-regular.ttf
├── glyphicons-halflings-regular.woff
├── glyphicons-halflings-regular.woff2
├── skin_all.css
├── skin_all.js
├── skin_black_dark.css
├── skin_black_dark.js
└── test.js

そういやfontとかも生成しているけどこれ、fileの配置のpath変えられるんだろうか。(fontは/dist/fontとか、jsは/dist/jsとか)

file配置の整理

file-loader系

github.com

&outputPath=app/images/をいれれば良さそう。

./dist
├── AdminLTE.css
├── AdminLTE.js
├── bootstrap.css
├── bootstrap.js
├── images
│   ├── boxed-bg.jpg
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── skin_all.css
├── skin_all.js
├── skin_black_dark.css
├── skin_black_dark.js
└── test.js

おお。できた。でもちょっと整理しよう。

imagesとfontsをわける。

      {
        test: /\.(jpg|png|gif)$/,
        loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
      },
      {
        test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        loader: 'file-loader?name=[name].[ext]&outputPath=/fonts/',
      }
./dist
├── AdminLTE.css
├── AdminLTE.js
├── bootstrap.css
├── bootstrap.js
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── images
│   └── boxed-bg.jpg
├── skin_all.css
├── skin_all.js
├── skin_black_dark.css
├── skin_black_dark.js
└── test.js

よすよす。

CSS

cssはこう。

const extractLess = new ExtractTextPlugin({filename: "css/[name].css"});

結果、

./dist
├── AdminLTE.js
├── bootstrap.js
├── css
│   ├── AdminLTE.css
│   ├── bootstrap.css
│   ├── skin_all.css
│   └── skin_black_dark.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── images
│   └── boxed-bg.jpg
├── skin_all.js
├── skin_black_dark.js
└── test.js

js

jsはどうしようかな……。

できればAdminLTEとかbootstrapとかの外部moduleと、自前のcomponentは分けたいんだけど。まずは全部/dist/jsに入れ込んでみる。

webpackのconfigをこうすると、

  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'js/[name].js'
  },

こうなる。

./dist
├── css
│   ├── AdminLTE.css
│   ├── bootstrap.css
│   ├── skin_all.css
│   └── skin_black_dark.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── images
│   └── boxed-bg.jpg
└── js
    ├── AdminLTE.js
    ├── bootstrap.js
    ├── skin_all.js
    ├── skin_black_dark.js
    └── test.js

……まあこれでもいいか。しいていうならcss生成の過程でうまれるskin類のjsがいらない。

環境構築に関する懸念

しかしこのままだと単にnpm installしただけだとbuildに失敗してしまう(AdminLTEのlessでjpgを見つけられないせいで)。

どうしようかな。やっぱりlessはまるごと持ってくる? それもなあ。

仕方ないのでmake setupというcommandを作って誤魔化すことにする。

この中でnpm installもすれば、node_modulesの中に出来たadmin-lteの中のfileの調整もしてくれるという算段。あんまり綺麗じゃないけど今はとりあえずこれでよし。

jsの組み込み?

あとはBootstrapとAdminLTEのjsの組み込み。

Bootstrapのjsって何してるんだろそういえば。

getbootstrap.com

Plugins can be included individually (using Bootstrap’s individual *.js files), or all at once (using bootstrap.js or the minified bootstrap.min.js).

ああjsで操作するためのpluginか。

ってこのままだと/dist/js以下にcssから生成されたjsが配置されてしまう。上書きのtimingが読めない以上避けたい。

stackoverflow.com

んー。これ見てる限りentryにいれちゃうと駄目なのかな……。

でもentryにいれないとcompileできないよな。どうしろと。

qiita.com

あっれ? modules.exportsってlistにして二ついれられるの? そういうものなの?

webpackのconfigの整理を始める

ということてmodules.exports周りをいじってみる。

cssというかlessをentryとして利用するものと、js関係をentryとして利用するものに分けて構築。

/config/entry.jsをこうする。

const path = require('path');
const jsEntry = {
  test: path.resolve(__dirname, '../src/root', 'test.jsx'),
};
const lessEntry = {
  skin_black_dark: path.resolve(__dirname, '../less/', 'skin-black-dark.less'),
  skin_all: path.resolve(__dirname, '../less/', '_all-skins.less'),
  bootstrap: path.resolve(__dirname, '../node_modules/bootstrap/less/', 'bootstrap.less'),
  AdminLTE: path.resolve(__dirname, '../node_modules/admin-lte/build/less/', 'AdminLTE.less'),
};
module.exports = {jsEntry, lessEntry};;

で、webpackのconfigをこうする。

const webpack = require('webpack');
const path = require('path');
const {jsEntry, lessEntry} = require('./entry');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractLess = new ExtractTextPlugin({filename: "css/[name].css"});
module.exports = [
  {
    entry: jsEntry,
    output: {
      path: path.resolve(__dirname, '../dist'),
      filename: 'js/[name].js'
    },
    devtool: 'inline-source-map',
    resolve: {
      extensions: ['.js', '.jsx'],
    },
    module: {
      rules: [
    {
      test: /\.jsx*$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    }
      ]
    },
  },
  {
    entry: lessEntry,
    output: {
      path: path.resolve(__dirname, '../dist'),
      filename: 'css/[name].css'
    },
    devtool: 'inline-source-map',
    module: {
      rules: [
    {
      test: /\.less$/,
      use: extractLess.extract({
        use: [
          { loader: "css-loader", options: { sourceMap: true } },
          { loader: "less-loader", options: { sourceMap: true } }
        ],
        fallback: "style-loader"
      })
    },
    {
      test: /\.(jpg|png|gif)$/,
      loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
    },
    {
      test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
      loader: 'file-loader?name=[name].[ext]&outputPath=/fonts/',
    }
      ]
    },
    plugins: [
      extractLess,
    ],
  }
];

通った。

    Hash: 497247a696149f8eb1b3
    Time: 12764ms
                                        Asset     Size  Chunks                    Chunk Names
      /fonts/glyphicons-halflings-regular.eot  20.1 kB          [emitted]
      /fonts/glyphicons-halflings-regular.svg   109 kB          [emitted]
      /fonts/glyphicons-halflings-regular.ttf  45.4 kB          [emitted]
     /fonts/glyphicons-halflings-regular.woff  23.4 kB          [emitted]
    /fonts/glyphicons-halflings-regular.woff2    18 kB          [emitted]
                         /images/boxed-bg.jpg   124 kB          [emitted]
                            css/bootstrap.css   651 kB    0, 0  [emitted]  [big]  bootstrap, bootstrap
                             css/AdminLTE.css   508 kB    1, 1  [emitted]  [big]  AdminLTE, AdminLTE
                      css/skin_black_dark.css  71.4 kB    2, 2  [emitted]         skin_black_dark, skin_black_dark
                             css/skin_all.css   360 kB    3, 3  [emitted]  [big]  skin_all, skin_all

fileも意図したとおりに。

./dist
├── css
│   ├── AdminLTE.css
│   ├── bootstrap.css
│   ├── skin_all.css
│   └── skin_black_dark.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── images
│   └── boxed-bg.jpg
└── js
    └── test.js

これを他にも反映する。あとwebpack-debug.config.jsをwebpack-devel.config.jsに名前を変えた。

develとproductionは修正完了。

webpack-dev-serverのconfig

webpack-dev-serverはどうしようかな……。

const path = require('path');
const {jsEntry, lessEntry} = require('./entry');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractLess = new ExtractTextPlugin({filename: "css/[name].css"});

module.exports = [
  {
    entry: jsEntry,
    output: {
      path: path.resolve(__dirname, '../dist'),
      filename: 'js/[name].js',
      publicPath: "/"
    },
    devtool: 'source-map',
    devServer: {
      contentBase: path.resolve(__dirname, '../pages'),
      port: 3000,
      proxy: {
    '/api': {
      target: 'http://localhost:3004',
      pathRewrite: {"^/api" : ""}
    }
      }
    },
    resolve: {
      extensions: ['.js', '.jsx']
    },
    module: {
      rules: [
    {
      test: /\.jsx*$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      options:{
        plugins: [
          'transform-runtime',
        ]
      }
    },
      ]
    },
  },
  {
    entry: lessEntry,
    output: {
      path: path.resolve(__dirname, '../dist'),
      filename: 'css/[name].css',
      publicPath: "/"
    },
    devtool: 'source-map',
    devServer: {
      contentBase: path.resolve(__dirname, '../pages'),
      port: 3000,
    },
    module: {
      rules: [
    {
      test: /\.less$/,
      use: extractLess.extract({
        use: [
          { loader: "css-loader", options: { sourceMap: true } },
          { loader: "less-loader", options: { sourceMap: true } }
        ],
        fallback: "style-loader"
      })
    },
    {
      test: /\.(jpg|png|gif)$/,
      loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
    },
    {
      test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
      loader: 'file-loader?name=[name].[ext]&outputPath=/fonts/',
    }
      ]
    },
    plugins: [
      extractLess,
    ]
  }
];

い、一応これで動きはしているけど。いいのかこれ。

でもdevServer定義しないとfile生成しようとしたりするしなあ……。

webpack.js.org

とりあえずこういう感じでmultiple targetが定義できることはわかった。

github.com

webpack-dev-serverで似たような話が出てる。

まあproxyしてしまうのが早いんだろうけどさあ。

んー。まあ動くからいいか。とりあえずは。

もうちょっと手入れしてみたけどこんな感じ。

const path = require('path');
const {jsEntry, lessEntry} = require('./entry');

const server = {
  contentBase: path.resolve(__dirname, '../pages'),
    port: 3000,
    proxy: {
      '/api': {
    target: 'http://localhost:3004',
    pathRewrite: {"^/api" : ""}
      },
    }
};

const jsTarget = {
  entry: jsEntry,
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'js/[name].js',
    publicPath: "/"
  },
  devtool: 'source-map',
  devServer: server,
  resolve: {
    extensions: ['.js', '.jsx']
  },
  module: {
    rules: [
      {
    test: /\.jsx*$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options:{
      plugins: [
        'transform-runtime',
      ]
    }
      },
    ]
  },
};


const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractLess = new ExtractTextPlugin({filename: "css/[name].css"});
const lessTarget = {
  entry: lessEntry,
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'css/[name].css',
    publicPath: "/"
  },
  devtool: 'source-map',
  devServer: server,
  module: {
    rules: [
      {
    test: /\.less$/,
    use: extractLess.extract({
      use: [
        { loader: "css-loader", options: { sourceMap: true } },
        { loader: "less-loader", options: { sourceMap: true } }
      ],
      fallback: "style-loader"
    })
      },
      {
    test: /\.(jpg|png|gif)$/,
    loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
      },
      {
    test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
    loader: 'file-loader?name=[name].[ext]&outputPath=/fonts/',
      }
    ]
  },
  plugins: [
    extractLess,
  ]
};


module.exports = [jsTarget,lessTarget];

考え中

これでcssを生成してもjsが作られることはなくなった。

……でもこれなら別にcompileしなくてもいい気がしてきた。bootstrapとかAdminLTEとか。file-loaderでdistにあるものをそのまま配置すればいいのでは。

いや一部に関してはそうもいかないか。skin周りは自前でいじったりするから。

うん。fileEntryとfileTargetもいれよう。……ってfile-loaderでそういう単なるfileのcopyは無理っぽい。というかそういう流れは想定されてない気がする。まあfileがちらばっちゃうもんな……。webpackの意義と正反対だし。

bug

ってあれ? 生成されたcssがjsになってしまってる。これじゃ駄目だ。

webpackのconfigを修正する。

const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractLess = new ExtractTextPlugin({filename: "css/[name].css"});
const lessTarget = {
  entry: lessEntry,
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'css/[name].css'
  },
  devtool: 'inline-source-map',
  module: {
    rules: [
      {
    test: /\.less$/,
    use: extractLess.extract({
      use: [
        { loader: "css-loader", options: { sourceMap: true } },
        { loader: "less-loader", options: { sourceMap: true } }
      ],
      fallback: "style-loader"
    })
      },
      {
    test: /\.(jpg|png|gif)$/,
    loader: 'file-loader?name=[name].[ext]&outputPath=/images/',
      },
      {
    test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
    loader: 'file-loader?name=[name].[ext]&outputPath=/fonts/',
      }
    ]
  },
  plugins: [
    extractLess,
  ],
};

こう修正することで一応大丈夫だったけど、これ上書きしているだけだよなぁ……。まあいいか。

このなんとも、もやっと感。

jsの組み込み

さてようやくjsの組み込みに入れる。

bootstrapやAdminLTEのsourceを見る。

うあー。jsみたらやっぱりbundleしてあげないと駄目なのかこれ。いやだぁ。<イマココ

あー。少なくともbootstrapについては特にこれで問題ないのか。jsで使うときはimportすればいいだけだし。

webpackになんとなくまとめてみる

const jsEntry = {
  test: path.resolve(__dirname, '../src/root', 'test.jsx'),
  adminlte: path.resolve(__dirname, '../node_modules/admin-lte/dist/js/', 'app.js'),
};

このように修正。これでcssの生成はするはず。

あっさり通った。

[0] Hash: 9a2f9af37034fbe48102497247a696149f8eb1b3
Version: webpack 2.3.3
Child
    Hash: 9a2f9af37034fbe48102
    Time: 19782ms
             Asset     Size  Chunks                    Chunk Names
        js/test.js   2.9 MB       0  [emitted]  [big]  test
    js/adminlte.js  67.4 kB       1  [emitted]         adminlte
       [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]
      [20] ./~/babel-runtime/helpers/possibleConstructorReturn.js 542 bytes {0} [built]
      [31] ./~/prop-types/index.js 647 bytes {0} [built]
      [88] ./~/babel-runtime/regenerator/index.js 49 bytes {0} [built]
     [117] ./~/react-router/es/index.js 637 bytes {0} [built]
     [174] ./~/redux-saga/lib/index.js 2.56 kB {0} [built]
     [185] ./src/pages/routing.jsx 3.04 kB {0} [built]
     [186] ./src/saga/test/index.js 2.05 kB {0} [built]
     [187] ./~/admin-lte/dist/js/app.js 23.3 kB {1} [built]
     [194] ./src/pages/base.jsx 3.19 kB {0} [built]
     [195] ./src/root/test.jsx 2.1 kB {0} [built]
        + 386 hidden modules

このあたりになるとdemo pageが動くか見ないとこれでいいのかわからなくなってくるなあ。

が、動作を見るとどうも半端なことになる。あれは動いてこれは動かないとか。

AdminLTEを完全にnodeの中に入れ込むのはつらい感じ。真面目にcodeの中身見て追いかける? ご冗談を。いや時間があるならやるけど3rd party libraryの扱いにそこまで力入れたくない。

どうしようか

やっぱりこれらのjsについては無理して入れ込まないことにする。今回は最低限cssの生成周りだけ入れ込めればいいという狙いだったから、それ以外は取り扱わない。

やっぱりdist以下を持ってくる方法を考えよう。

結論

いろいろ考えたけど必要な物はmakefile使って/pages以下に手動で配置することにする。

distributeする時もこれを巻き込めるようにしてしまえばいい。どうせmakefileからmake allで生成するようにしているし。

bootstrapはnode_modulesから/distの中身をcopyすればいいかな。

admin-lteも基本的にはそうだけど、こいつの場合、lessからskin周りのcss作り直しているのがちょっとやっかい。

といっても/dist/css/skinsの中に全部配置されているし、これらだけ置き換えればいいか。

よしよし。この方針で行こう。

これでよし。一応make setupとmake allにも入れ込んだ(make allの時は/dist以下に展開される)

% make 3rdparty
mkdir -pv ./pages/3rdparty
cp -r ./node_modules/admin-lte/dist ./pages/3rdparty/adminlte
cp -r ./node_modules/admin-lte/plugins ./pages/3rdparty/adminlte/plugin
rm -r ./pages/3rdparty/adminlte/css/skins
cp -r ./node_modules/bootstrap/dist ./pages/3rdparty/bootstrap
for path in `find ./pages/3rdparty -type f | grep "map$"`; do \
    rm $path; \
    done
for path in `find ./pages/3rdparty -type f | grep "js$" | grep -v ".min."`; do \
    rm $path; \
    done
for path in `find ./pages/3rdparty -type f | grep "css$" | grep -v ".min."`; do \
    rm $path; \
    done

あとはAdminLTEのindex2.htmlをcopyして、cssやjsのaddressを修正して読み込ませることに成功。

無事動作したので環境としてはこれで良かろうなのだ……。

その前に最後の調整。

さすがに/distに自分で作ったわけでもないlibraryをいれるのはひどいのでmake installの時に入れ込むようにしてmake allから排除して、終わり。

次のstep

とはいえこれだけで終わりとするのはちょっと雑。

いやpageを作るだけならこれでいいんだけど、どうせreactでやっているんだから、componentごとに動作の様子見とかしたいよね。もっと気楽に。

ということでstorybookの導入を試みることにする。それができたらgithubにあげる予定。