かんたんパッケージマネージャDuo | サイバーエージェント 公式エンジニアブログ

テーマ:

みなさん、こんにちは。Ameba事業本部ゲーム部門の平木(Layzie) と申します。

最近はSteamで安いゲームを漁ってばかりの毎日です。このエンジニアブログでは初めて執筆になります。



さて、今回エンジニアブログで何を書こうか悩んだのですが、悩んだ結果Duoというパッケージマネージャの紹介をしようということになりました。

このDuo、GitHubのStar数は結構多いんですが、(2014/11現在2618Star)あまり紹介されてる記事とかが無いので紹介してみようと思った次第です。



Duo


Duoの特徴



昨今、フロントエンドで使えるパッケージマネージャは色々あると思います。厳密に言うとサーバ側のJavaScript実装ですがNode.jsのパッケージマネージャであるnpmにフロントのJavaScriptライブラリを登録して使うというパターンもありますし、新規プロジェクトの雛形を作ってくれるYeomanでも採用されているTwitter社が作っているbowerなども有名です。そもそも最初からフロントエンドで使うHTML/CSS/JavaScriptをセットで扱いやすくするcomponentなどが有名でしょう。(余談ですが、componentとかググラビリティが低いので調べものの時に苦労しますよね)

最近ではもう何でも入れてやる位の意気込みを感じるwebpackなどもあります。



そんな中出てきたDuoの一番の特徴は GitHubにアップされているリポジトリをそのままパッケージとして使用する というところが挙げられます。次に package.jsonなどの設定ファイルが不要 という2点が既存のパッケージマネージャと違う部分ではないかと思います。



公式サイトでの紹介では、component、Browserify、そしてGoの影響を受けていると書いてますが、確かにこれらの良いところどりな感じのフロントエンドパッケージマネージャになっています。



インストール



インストールはnpmで行うことができます。もはやお馴染みの感じじゃないかと思うんですが、以下のコマンドでインストールしていきます。



$ npm install -g duo


他のパッケージマネージャと違いこのDuoは使う前に.netrcファイルにGitHub APIを使うための設定が必要です。API制限の回避や、プライベートリポジトリを使用するための措置です。



machine api.github.com
login <YOUR ID>
password <YOUR TOKEN>


passwordに入れるのはGitHubのアクセストークンなので、こちらから設定してあげてください。自分はデフォルトの権限にしています。



使用例



さて、実際に簡単なプロジェクトを作って使ってみることにします。まずは公式のソースをちょっとアレンジしてHTMLとJavaScriptを書いてみます。



index.html



<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Duo.js Test</title>
</head>
<body>
<p>Your ID:</p>
<p class="display"></p>
<script src="build.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>


index.js



var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');

var msg = fmt('Your unique ID is %s!', uid());

var display = document.querySelector('.display');
var text = document.createTextNode(msg);

display.appendChild(text);


ユニークなIDを生成して文字列のフォーマットをしてそれをHTMLに出力するという簡単なものですね。ここでお気付きの方もいると思いますが、JSの方の12行目でCommonJSライクにrequire()している部分がありますが、注目したいのは中の文字列です。'matthewmueller/uid''yields/fmt'という形で呼び出しています。これはそれぞれ、uidfmtというcomponentのライブラリをGitHubから直接呼び出ししているのです。require()したあとは普通に使っています。



またHTMLに今はないbuild.jsを読み込んでいますが、これはこれからduoコマンドを使ってビルドしたあとのJavaScriptになります。それではビルドしましょう。



$ duo index.js > build.js

building : index.js
installed : matthewmueller-uid@0.0.2
installed : yields-fmt@0.1.0
built : index.js


このように、初回のみなのですがJavaScriptでrequire()したライブラリを勝手にインストールしてくれます。元々はindex.htmlindex.jsしか無い構成だったのですが、ビルド後には勝手にcomponents/というディレクトリが切られて以下のようになります。



.
├── build.js
├── components
│ ├── duo.json
│ ├── matthewmueller-uid@0.0.2
│ │ ├── History.md
│ │ ├── Makefile
│ │ ├── Readme.md
│ │ ├── bin
│ │ │ └── uid
│ │ ├── component.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── test.html
│ └── yields-fmt@0.1.0
│ ├── History.md
│ ├── Makefile
│ ├── Readme.md
│ ├── component.json
│ ├── index.js
│ └── test
│ ├── index.html
│ └── test.js
├── index.html
└── index.js

5 directories, 19 files


components/内に上記で紹介したライブラリがダウンロードされているのが分かりますね。ビルド後のJSは以下のようになっています。



(function outer(modules, cache, entries){

/**
* Global
*/

var global = (function(){ return this; })();

/**
* Require `name`.
*
* @param {String} name
* @param {Boolean} jumped
* @api public
*/

function require(name, jumped){
if (cache[name]) return cache[name].exports;
if (modules[name]) return call(name, require);
throw new Error('cannot find module "' + name + '"');
}

/**
* Call module `id` and cache it.
*
* @param {Number} id
* @param {Function} require
* @return {Function}
* @api private
*/

function call(id, require){
var m = cache[id] = { exports: {} };
var mod = modules[id];
var name = mod[2];
var fn = mod[0];

fn.call(m.exports, function(req){
var dep = modules[id][1][req];
return require(dep ? dep : req);
}, m, m.exports, outer, modules, cache, entries);

// expose as `name`.
if (name) cache[name] = cache[id];

return cache[id].exports;
}

/**
* Require all entries exposing them on global if needed.
*/

for (var id in entries) {
if (entries[id]) {
global[entries[id]] = require(id);
} else {
require(id);
}
}

/**
* Duo flag.
*/

require.duo = true;

/**
* Expose cache.
*/

require.cache = cache;

/**
* Expose modules
*/

require.modules = modules;

/**
* Return newest require.
*/

return require;
})({
1: [function(require, module, exports) {
var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');

var msg = fmt('Your unique ID is %s!', uid());

var display = document.querySelector('.display');
var text = document.createTextNode(msg);

display.appendChild(text);

}, {"matthewmueller/uid":2,"yields/fmt":3}],
2: [function(require, module, exports) {
/**
* Export `uid`
*/

module.exports = uid;

/**
* Create a `uid`
*
* @param {String} len
* @return {String} uid
*/

function uid(len) {
len = len || 7;
return Math.random().toString(35).substr(2, len);
}

}, {}],
3: [function(require, module, exports) {

/**
* toString.
*/

var toString = window.JSON
? JSON.stringify
: function(_){ return String(_); };

/**
* Export `fmt`
*/

module.exports = fmt;

/**
* Formatters
*/

fmt.o = toString;
fmt.s = String;
fmt.d = parseInt;

/**
* Format the given `str`.
*
* @param {String} str
* @param {...} args
* @return {String}
* @api public
*/

function fmt(str){
var args = [].slice.call(arguments, 1);
var j = 0;

return str.replace(/%([a-z])/gi, function(_, f){
return fmt[f]
? fmt[f](args[j++])
: _ + f;
});
}

}, {}]}, {}, {"1":""})


中身はDuoで使う諸々の設定と読み込んだライブラリ、実際に自分が書いたJSが一緒になっているのが分かります。HTMLを見てみるとリロードごとにIDが変っていくのが分かると思います。



uid


基本の使用例はこのような感じなんで、とても簡単に使えるのが分かると思います。やっぱりpackage.jsonなどを書かないでもライブラリを使えるのは手軽に試したいときなどには重宝しますね。



Duoができること



ここからは基本使用例以外で、Duo.jsができることを軽く紹介していこうかと思います。



ローカルファイルの読み込み



先程のJSを以下のように変更するとローカルの別のファイルを読み込むことが可能になります。ここでは若干ムリヤリ感ありますが、display.jsを作ってここでDOM操作を担当します。index.jsではそれを呼び出すことにします。



index.js



var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');
var display = require('./display');

var msg = fmt('Your unique ID is %s!', uid());

display('.display', msg);


display.js



function display(selector, text) {
var target = document.querySelector(selector),
msg = document.createTextNode(text);

target.appendChild(msg);
}

module.exports = display;


ビルドします。



$ duo index.js > build.js

building : index.js
built : index.js


HTMLをリロードしても先程と変わりなくユニークIDが見れると思います。



ライブラリのバージョン指定しての読み込み



以下のようにするとバージョンを指定してライブラリをrequire()することができます。



var reactive = require('component/reactive@0.14.x'); // 0.14系最新版を使う
var tip = require('component/tip@master'); // リポジトリのmasterブランチを使う
var shortcuts = require('yields/shortcuts@0.0.1:/index.js'); // 0.0.1を使いエントリポイントとして、index.jsを使う


bowerで作られているライブラリに対しては特に一番下のようにエントリポイントとしてメインのJSを指定しないと上手く動かないことがあります。



CSSもJSと同じようにライブラリを読み込みできる



componentなどでもできますが、DuoではCSSも紹介してきたような方法で同様にライブラリを呼び出しすることができます。



先程のHTMLにCSSを加えてみましょう



index.html



<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Duo.js Test</title>
<link rel="stylesheet" href="build.css" type="text/css" media="all" />
</head>
<body>
<p>Your ID:</p>
<p class="display"></p>
<script src="build.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>


index.css



@import 'necolas/normalize.css';

p {
font-size: 20px;
}

.display {
font-size: 32px;
color: red;
}


CSSの場合はrequire()の代わりに(当たりまえですけど)@importを使います。ここでは有名なnormalize.cssを呼び出してビルドしてみます。



$ duo index.css > build.css

building : index.css
installed : necolas-normalize.css@3.0.1
built : index.css


CSSも先程JSライブラリがインストールされたcomponents/の下にインストールされます。



.
├── build.css
├── build.js
├── components
│ ├── duo.json
│ ├── matthewmueller-uid@0.0.2
│ │ ├── History.md
│ │ ├── Makefile
│ │ ├── Readme.md
│ │ ├── bin
│ │ │ └── uid
│ │ ├── component.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── test.html
│ ├── necolas-normalize.css@3.0.1
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE.md
│ │ ├── README.md
│ │ ├── bower.json
│ │ ├── component.json
│ │ ├── normalize.css
│ │ ├── package.json
│ │ └── test.html
│ └── yields-fmt@0.1.0
│ ├── History.md
│ ├── Makefile
│ ├── Readme.md
│ ├── component.json
│ ├── index.js
│ └── test
│ ├── index.html
│ └── test.js
├── display.js
├── index.css
├── index.html
└── index.js

6 directories, 31 files


ビルドされたCSSにはきちんとnormalize.cssが含まれているのが分かります。



build.css



/*! normalize.css v3.0.1 | MIT License | git.io/normalize */

/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/

html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}

/**
* Remove default margin.
*/

body {
margin: 0;
}

/* HTML5 display definitions
========================================================================== */

/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}

/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/

audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}


/*
* 途中省略
*/

p {
font-size: 20px;
}

.display {
font-size: 32px;
color: red;
}


HTMLを見てみましょう。スタイルが変更されているのが分かります。



uid2


他にも、ビルド中にsassなどを処理できるプラグイン機構も搭載されていたりするのも良いです。主要なCSSプリプロセッサなどは使えるようになっていますね。



まとめ



簡単にですが、Duoについて紹介しました。このような簡易なスタイルでパッケージ管理をあまり意識せずに開発が進められるのが一番の魅力なんではないかと思います。segment.ioが関わっているようなので、componentの上位互換のような匂いがします。



あとは、参考にもされているBrowserifyとの違いですが、Node.jsのAPIを使いたいかどうか?というところが主に大きい違いになると思います。



自分も業務ではまだDuoを使っていないので、中・大規模な開発に耐えられるのか?というようなところはまだ未検証なのですが、例えば、小規模の開発などやモックを作るなど試行錯誤が必要な場合などには、設定ファイルがないという身軽さが生きてくるパッケージマネージャではないかなと考えています。



ということで、みなさんもぜひ試してみてはいかがでしょうか?

サイバーエージェント 公式エンジニアブログさんの読者になろう

ブログの更新情報が受け取れて、アクセスが簡単になります

Copyright © CyberAgent, Inc. All Rights Reserved.