bower installするとなんかいろいろ落ちてくる。
ある時思った。HTMLページ書くのでjQueryでも使うか。
でもダウンロードするのめんどくさい。そうだ、bowerがある。
$ bower install jquery
で一発だぜ。
さて、HTMLからjQuery読み込むか。
あれ、どこだ?
bower_components
└─ jquery
├─ dist
| └─ ...
├─ src
| └─ ...
├─ bower.json
├─ .bower.json
├─ bower.json
└─ MIT-LICENSE.txt
bower
でインストールすると、パッケージのすべてのファイルがまとめてbower_components
ディレクトリしたに配置される。
jquery.js
が欲しければbower_components/jquery/dist/jquery.js
を自分で探さなければならない。
やだなにこれうざい。jquery.js
が欲しいだけなのに。
必要なファイルだけさくっとダウンロードして配置したい。
きちんと管理するならbower.jsonにあれこれ書いてversion指定して、云々となるけどあんまりそんなことしたくなかったりする。
パッケージ情報ならpackage.json
に書いてあるし。bower.json
(component.json
) に同じようなこと書きたくないし。
サクッと必要なファイルだけ拾ってきて必要な場所に置けるようにしたい。
なんかそんな裏技ないかとbowerのAPIページをしばし眺める。
ない。
全部見たわけじゃやないけど多分ない。気がする。
でも代わりにProgrammatic APIが公開されているのを見つけた。
じゃあ作るか。
必要なファイルだけさくっとダウンロードして配置する簡易コマンドを設計する。
パッケージ名と配置先を指定すると、勝手にダウンロードして置いてくれる感じにしたい。
これをhandybower
と名付けよう。
使い方は、
# jqueryの最新版と、angularのv1.4.3を"app/public/vendor"ディレクトリに配置する
$ handybower jquery angular#1.4.3 -d app/public/vendor
的な。
ついでにjsからも、
#!/usr/bin/env node
var handybower = require('handybower');
handybower([
"jquery",
"angular#1.4.3"
], {
dest: "app/public/vendor"
}, function (err) {
});
的な感じで呼べるとなお素敵。
必要なファイルだけさくっとダウンロードして配置する簡易コマンドを実装する
bowerのProgrammatic APIのページを見ると、
こんな感じでインストールができるようだ。
bower.commands
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
インストールされたパッケージの直下にはbower.json
がいて、その中のmain
属性に
外に公開したいファイルが書かれている。
よし、これを利用しよう。
まずはこんな感じで、jsの関数を用意する。
/**
* Install bower components and collect main files
* @function handyBower
* @param {string|string[]} name - Module name to install.
* @param {object} options - Optional settings.
* @param {string} [options.dest='handybower_components'] - Destination directory path
* @param {boolean} [options.verbose=false] - Log verbose.
* @param {string[]} [options.main] - Name of main files. Default is `main` attribuite in installed bower.json.
* @param {function} callback - Callback when done.
*/
"use strict";
var argx = require('argx'),
writeout = require('writeout'),
fs = require('fs'),
path = require('path'),
glob = require('glob'),
async = require('async'),
bower = require('bower'),
Colorprint = require('colorprint/lib/colorprint');
function handyBower(names, options, callback) {
var args = argx(arguments);
callback = args.pop('function') || argx.noop;
options = args.pop('object');
names = args.remain().reduce(_concat, []).map(function (name) {
if (typeof(name) === 'object') {
var data = name;
return Object.keys(data).map(function (name) {
return [name, data[name]].join('#');
});
} else {
return String(name);
}
}).reduce(_concat, []);
var logger = new Colorprint({
PREFIX: '[handybower] '
});
var verbose = !!options.verbose,
main = [].concat(options.main || '')
.reduce(_concat, [])
.filter(_notEmpty);
if (verbose) {
logger.trace('options:\n', options);
}
var destDir = options.dest || 'handybower_components';
if (!destDir) {
callback(new Error('dest is required.'));
return;
}
var aborted = false;
var components = {};
logger.info('Installing started...');
bower.commands
.install(names, {
force: true
}, {
interactive: false
})
.on('error', function (err) {
callback(err);
aborted = true;
})
.on('log', function (packet) {
var data = packet.data || {};
var endpoint = data.endpoint || {};
var name = endpoint.name;
components[name] = components[name] || data.canonicalDir;
})
.on('end', function (installed) {
if (aborted) {
return;
}
Object.keys(installed).forEach(function (name) {
components[name] = components[name] || installed[name]['canonicalDir'];
});
var dirnames = Object.keys(components).map(function (name) {
return components[name];
});
async.eachSeries(dirnames, function (dirname, callback) {
var data = require(path.resolve(dirname, 'bower.json'));
var found = [].concat(main && main.length ? main : data.main).map(function (pattern) {
return glob.sync(pattern, {cwd: dirname});
}).reduce(_concat, []);
if (verbose) {
logger.trace('main files:\n%s', found);
}
async.eachSeries(found, function (filename, callback) {
var src = path.resolve(dirname, filename),
dest = path.resolve(destDir, path.basename(filename));
async.waterfall([
function (callback) {
fs.readFile(src, callback);
},
function (content, callback) {
writeout(dest, String(content), {
mkdirp: true
}, callback);
},
function (result, callback) {
if (!result.skipped) {
logger.debug('File generated:', dest);
}
callback(null);
}
], callback);
}, callback);
}, function (err) {
if (err) {
console.error(err);
logger.error('...installing failed!');
} else {
logger.info('...installing done!');
}
callback(err);
});
});
}
function _concat(a, b) {
return a.concat(b);
}
function _notEmpty(val) {
return !!val;
}
module.exports = handyBower;
次にこれを、commanderを使ってCLIから呼べるようにする。
#!/usr/bin/env node
/**
* @file Bins script for handybower.
*/
"use strict";
var program = require('commander'),
pkg = require('../package'),
handybower = require('../handybower');
program
.usage('[options] [name ...]')
.version(pkg['version'])
.option('-d, --dest <dest>', "Destination directory path.")
.option('-v, --verbose', "Show verbose log.")
.parse(process.argv);
var moduleNames = program.args;
handybower(moduleNames, {
verbose: program.verbose,
dest: program.dest,
main: program.main && (program.main).split(/,/g)
});
よしできた。
実装したコマンドをnpmレポジトリで公開する
あとはpackage.jsonをちゃちゃっと整理してテストを書いてそのままnpmモジュールとして公開
$ npm install handybower -g
これでちょろっと使うのが楽になる。