LoginSignup
7
8

More than 5 years have passed since last update.

bowerから必要なファイルだけとってきて必要な場所に配置するようにする

Posted at

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

これでちょろっと使うのが楽になる。

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8