初心に帰れるうちが花
今後の uupaa.js の形を決めました。
これまで
- uupaa.js ファイルに必要と思われる機能をギチギチに詰め込んでる。
- コードの可読性が低い + version間でdiff取れない(弄りすぎ)。
- uu.module() でモジュールを読み込むと色々機能が増えるみたいだけど、実用性はほとんどない。
これから
- 1ファイルに詰め込むやり方じゃ、だめだぁ〜。
- uupaa.js はビルドして作り出すファイルになる。
- 最小構成でビルドすると 500〜600行ぐらいに。
- 核 は 100行ぐらい。
- 最小構成でビルドすると 500〜600行ぐらいに。
- 不足している機能は、uu.feat() でオンデマンドロードすると使えるようになる。
- 依存関係の管理は、uu.feat() がやってくれる。
- コードのリライトは必要最小限に
- 部品を大幅に書き換えたり、互換性が失われる場合は、ファイル名を変更する
- ビルダーを提供する
- make ファイル相当のものか、サイト上で必要な機能を選択しビルドする機能を提供する
- 多数のファイルをネットワーク越しに読み込みたくない人は、必要な機能を詰め込んで 1ファイル化 してしまえるようにする。
- make ファイル相当のものか、サイト上で必要な機能を選択しビルドする機能を提供する
0.7 以降で解決する(解決したい)問題
- 依存関係を気にしたくない
- 詰め込みたくない
- 依存関係を気にする必要が無くなると、詰め込む必要も無くなる
- 1ファイルが100〜500stepぐらいの大きさになるので、Firebug もサクサク動いてくれる
- 誰にでも読めるような平易なコードをそのままリリースできる。
具体的にはどんな感じになるのさ?
手元のやつはこんな感じ
// uupaa.js version 0.7.0α if (!window.uu) { // === Global namespace pollution ========================== var uu, // core and class namespace UU; // const and config namespace // === Core ================================================ window.uu = uu = function() { return uu._impl.apply(this, arguments); // adapter }; window.UU = UU = { VERSION: [0, 7, 0], CONFIG: { AUTO_RUN: "boot", // uu.ready(window.boot) function FEAT_URL1: "src", // primary url FEAT_URL2: "", // secondary url FEAT_TIMEOUT: 20000, // timeout(unit: ms) - 20sec QUERY_CACHE: 1, // 1: enable cache DEBUG: 0 // 1: debug mode } }; // uu.mix - mixin uu.mix = function(base, flavor, aroma, override /* = true */) { var i, ride = (override === void 0) || override; for (i in flavor) { if (ride || !(i in base)) { base[i] = flavor[i]; } } return aroma ? uu.mix(base, aroma, null, ride) : base; }; uu.mix(uu, { // uu.uuid - unique-id generator uuid: function() { return ++uu._uuid; }, _uuid: 0, // uu.id - id query id: function(id) { // tiny return document.getElementById(id); }, // uu.tag - tag query tag: function(tag, context /* = document */) { // tiny var ary = (context || document).getElementsByTagName(tag), rv = [], ri = -1, v, i = 0, iz = ary.length; for (; i < iz; ++i) { v = ary[i]; v.nodeType === 1 && (rv[++ri] = v); } return rv; }, // uu.className - className query className: function(expr, context /* = document */, really /* = false */) { return uu.className.query(expr, context, really); // adapter }, // uu.css - css query css: function(expr, context /* = document */, really /* = false */) { return uu.css.query(expr, context, really); // adapter }, // uu.feat - load feature feat: function(feat, fn, url1 /* = "" */, url2 /* = "" */) { uu.feat._impl(feat, fn, url1 || UU.CONFIG.FEAT_URL1, url2 || UU.CONFIG.FEAT_URL2); // adapter }, // uu.base - ref base directory base: function() { if (!uu._base) { uu._base = location.protocol + "//" + location.pathname.replace(/\\/g, "/"); uu.tag("script").forEach(function(v) { if (/uupaa.*\.js$/.test(v.src)) { var elm = document.createElement("div"), ary; elm.innerHTML = '<a href="' + v.src + '" />'; ary = elm.firstChild.href.split("/"); // "http://example.com/dir/uupaa.js" ary.pop(); // drop file name "uupaa.js" uu._base = ary.join("/"); // "http://example.com/dir" } }); } return uu._base; }, _base: "", // uu.head - ref <head> tag head: document.getElementsByTagName("head")[0] }); uu.feat.list = { // feat : "depend, ..." core : "xbrowser,feat,ready", query : "core,oop,type,ua,aid,node,attr,viewport,css,classname,query.quick", "query+" : "query", j : "core,xbrowser+,event,query,query+" }; // === Cross Browser ======================================= // depend: none // --- Array.prototype Cross Browser --- uu.mix(Array.prototype, { // Array.prototype.lastIndexOf lastIndexOf: function(needle, fromIndex /* = this.length */) { var iz = this.length, i = fromIndex; i = (i < 0) ? i + iz : iz - 1; for (; i > -1; --i) { if (i in this && this[i] === needle) { return i; } } return -1; }, // Array.prototype.indexOf indexOf: function(needle, fromIndex /* = 0 */) { var iz = this.length, i = fromIndex || 0; i = (i < 0) ? i + iz : i; for (; i < iz; ++i) { if (i in this && this[i] === needle) { return i; } } return -1; }, // Array.prototype.forEach forEach: function(fn, bindThis /* = undefined */) { var i = 0, iz = this.length; for (; i < iz; ++i) { if (i in this) { fn.call(bindThis, this[i], i, this); } } }, // Array.prototype.filter filter: function(fn, bindThis /* = undefined */) { var rv = [], ri = -1, v, i = 0, iz = this.length; for (; i < iz; ++i) { if (i in this) { v = this[i]; if (fn.call(bindThis, v, i, this)) { rv[++ri] = v; } } } return rv; }, // Array.prototype.every every: function(fn, bindThis /* = undefined */) { var i = 0, iz = this.length; for (; i < iz; ++i) { if (i in this) { if (!fn.call(bindThis, this[i], i, this)) { return false; } } } return true; }, // Array.prototype.some some: function(fn, bindThis /* = undefined */) { var i = 0, iz = this.length; for (; i < iz; ++i) { if (i in this) { if (fn.call(bindThis, this[i], i, this)) { return true; } } } return false; }, // Array.prototype.map map: function(fn, bindThis /* = undefined */) { var rv = Array(this.length), i = 0, iz = this.length; for (; i < iz; ++i) { if (i in this) { rv[i] = fn.call(bindThis, this[i], i, this); } } return rv; } }, 0, 0); // --- String.prototype Cross Browser --- uu.mix(String.prototype, { // String.prototype.trim - from Firefox3.1 trim: function() { return this.replace(/^\s+|\s+$/g, ""); } }, 0, 0); // === Feature ============================================= // depend: xbrowser uu.mix(uu.feat, { // uu.feat._impl _impl: function(feat, fn, url1, url2) { function DIE(miss) { throw TypeError("uu.feat: " + miss); } uu.feat.from(url1).load(feat, function(ok, miss) { if (ok) { fn(); // loaded } else { !url2 && DIE(miss); uu.feat.from(url2).load(miss, function(ok, miss) { !ok && DIE(miss); fn(); // safeguard }); } }); }, // uu.feat.from - set feature(script file) base path from: function(url) { uu.feat._from = url.replace(/\/+$/, ""); // trim tail "/" return uu.feat; }, _from: uu.base(), // uu.feat.load - load feature load: function(feat, fn /* = undefined */) { var feats = [], jobid = uu.uuid(), tm = UU.CONFIG.FEAT_TIMEOUT; // collect dependency list feat.split(",").forEach(function(v) { uu.feat._collect(v.trim(), feats); }); if (feats.length) { uu.feat._job[jobid] = { fn: fn, feats: feats, timeout: +new Date + tm }; uu.feat._runner(jobid); setTimeout(uu.feat._watchdog, tm); } else { fn(true, ""); // already loaded } }, // uu.feat.already already: function(feat) { // feat = "feat" or "feat, feat, ..." return feat.split(",").every(function(v) { return v.trim() in uu.feat; }); }, // uu.feat.mix - mix feature-list(override) mix: function(featureList) { uu.mix(uu.feat.list, featureList); return uu.feat; }, // job database _job: { /* jobid: { fn, feats, timeout } */ }, // status keeper _run: { core: 2, xbrowser: 2, feat: 2, ready: 2 }, // 1: loading, 2: loaded _collect: function(feat, rv) { var run = uu.feat._run[feat] || (uu.feat._run[feat] = 0); if (!run) { // loading(1) or loaded(2) -> skip (rv.indexOf(feat) < 0) && rv.push(feat); // pushed -> skip if (feat in uu.feat.list) { uu.feat.list[feat].split(",").forEach(function(v) { uu.feat._collect(v.trim(), rv); // recursive call }); } } }, _runner: function(jobid) { uu.feat._job[jobid].feats.forEach(function(v) { if (uu.feat._run[v] || uu.feat._isDepend(v)) { // skip or lazy return; } // build script element // <script id="{feat}.js" type="text/javascript" charset="utf-8"> var scr = document.createElement("script"); scr.id = v + ".js"; scr.type = "text/javascript"; scr.charset = "utf-8"; if (document.uniqueID) { // IE scr.onreadystatechange = function() { if (/loaded|complete/.test(this.readyState)) { (v in uu.feat) ? uu.feat._done(jobid, v) : uu.feat._kill(jobid); } }; } else { scr.onload = function() { (v in uu.feat) ? uu.feat._done(jobid, v) : uu.feat._kill(jobid); }; scr.onerror = function() { uu.feat._kill(jobid); }; } scr.setAttribute("src", uu.feat._from + "/" + v + ".js"); uu.head.appendChild(scr); uu.feat._run[v] = 1; }); }, _isDepend: function(feat) { if (feat in uu.feat.list) { return uu.feat.list[feat].split(",").some(function(v) { return uu.feat._run[v.trim()] !== 2; // depend }); } return 0; // not depend }, _done: function(jobid, feat) { uu.feat._run[feat] = 2; // loaded var job = uu.feat._job[jobid], fn = job.fn; if (job) { job.feats.splice(job.feats.indexOf(feat), 1); // delete if (job.feats.length) { uu.feat._runner(jobid); // next } else { delete uu.feat._job[jobid]; // delete job fn && fn(true, ""); // load complete } } else { throw Error("lost job"); } }, _kill: function(jobid) { var job = uu.feat._job[jobid], fn = job.fn, feats; // remove <script id="{feat}.js"> element job.feats.forEach(function(v, i) { var e = document.getElementById(v + ".js"); e && e.parentNode.removeChild(e); }); feats = job.feats.join(","); delete uu.feat._job[jobid]; fn && fn(false, feats); // fail }, _watchdog: function() { var run = 0, time = +new Date, jobid; for (jobid in uu.feat._job) { if (time > uu.feat._job[jobid].timeout) { uu.feat._kill(jobid); } else { ++run; } } run && setTimeout(uu.feat._watchdog, UU.CONFIG.FEAT_TIMEOUT); } }); // === Ready =============================================== // depend: none uu.mix(uu, { // uu.ready - ready event handler ready: function(fn) { (function LOOP() { uu.domReady ? fn() : setTimeout(LOOP, 0); })(); }, // uu.domReady - DomReady state(1: ready, 0: not ready) domReady: 0, // uu.unready - unready event handler unready: function(fn) { document.uniqueID ? attachEvent("onunload", fn) // IE : addEventListener("unload", fn, false); } }); (function(_doc, navi, fn) { if (_doc.uniqueID || (/WebKit/.test(navi) && _doc.readyState)) { (function LOOP() { var ok = 0; try { if (_doc.uniqueID) { // IE // http://javascript.nwbox.com/IEContentLoaded/ ok = _doc.documentElement.doScroll("up") || 1; } else { ok = /loaded|complete/.test(_doc.readyState); } } catch(err) {} ok ? fn() : setTimeout(LOOP, 0); })(); } else if (/Gecko\/|Opera/.test(navi)) { _doc.addEventListener("DOMContentLoaded", fn, false); } else { // for legacy browser addEventListener("load", fn, false); } })(document, navigator.userAgent, function() { ++uu.domReady; }); // --- boot loader --- uu.ready(function() { var fn = window[UU.CONFIG.AUTO_RUN]; fn && fn(); }); } // if (window.uu)
こんな感じに使います。
<html><head><title>uupaa.js 0.7</title> <script src="uupaa-0.7.js"></script> </head> <body> <script> function boot() { uu.feat("jQuery,canvas,widget", function() { // alert("A Happy new year 2009"); }); } </script> </body></html>