React+StoryBook+AdminLTEでのComponent開発環境(2)
前回
- 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系
&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って何してるんだろそういえば。
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が読めない以上避けたい。
んー。これ見てる限りentryにいれちゃうと駄目なのかな……。
でもentryにいれないとcompileできないよな。どうしろと。
あっれ? 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生成しようとしたりするしなあ……。
とりあえずこういう感じでmultiple targetが定義できることはわかった。
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ごとに動作の様子見とかしたいよね。もっと気楽に。