Browse Source

v2 json page auto format

zxlie 7 years ago
parent
commit
4e337c1ca5

+ 6 - 1
v2.0/apps/ajax-debugger/index.js

@@ -9,7 +9,7 @@
  */
 let AjaxDebugger = (function () {
 
-    let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+    let MSG_TYPE = Tarp.require('../static/js/msg_type');
 
     /**
      * 自定义Console
@@ -112,4 +112,9 @@ let AjaxDebugger = (function () {
 
     });
 
+    // 与background保持心跳
+    chrome.runtime.connect({
+        name: MSG_TYPE.DEV_TOOLS
+    });
+
 })();

+ 34 - 2
v2.0/apps/background/index.js

@@ -4,9 +4,10 @@
  */
 var BgPageInstance = (function () {
 
-    let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+    let MSG_TYPE = Tarp.require('../static/js/msg_type');
     let Settings = Tarp.require('../options/settings');
     let feHelper = {};
+    let devToolsDetected = false;
 
     // debug cache,主要记录每个tab的ajax debug 开关
     let ajaxDbgCache = {};
@@ -141,13 +142,26 @@ var BgPageInstance = (function () {
     /**
      * 告诉DevTools页面,当前的debug开关是否打开
      * @param callback
+     * @param withAlert
      * @private
      */
-    let _tellDevToolsDbgSwitchOn = function (callback) {
+    let _tellDevToolsDbgSwitchOn = function (callback, withAlert) {
 
         chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
             let tab = tabs[0];
             callback && callback(ajaxDbgCache[tab.id]);
+
+            if (withAlert) {
+                if (ajaxDbgCache[tab.id]) {
+                    let msg = '';
+                    if (devToolsDetected) {
+                        msg = 'DevTools已打开,确保已切换到【Console】界面,并关注信息输出,愉快的进行Ajax Debugger!'
+                    } else {
+                        msg = '请打开DevTools,并切换到【Console】界面,关注信息输出,愉快的进行Ajax Debugger!';
+                    }
+                    alert(msg);
+                }
+            }
         });
     };
 
@@ -459,6 +473,24 @@ var BgPageInstance = (function () {
 
             return true;
         });
+
+        // 检测DevTools是否打开
+        let openCount = 0;
+        chrome.runtime.onConnect.addListener(function (port) {
+            if (port.name === MSG_TYPE.DEV_TOOLS) {
+                if (openCount === 0) {
+                    devToolsDetected = true;
+                }
+                openCount++;
+
+                port.onDisconnect.addListener(function (port) {
+                    openCount--;
+                    if (openCount === 0) {
+                        devToolsDetected = false;
+                    }
+                });
+            }
+        });
     };
 
     /**

+ 1 - 1
v2.0/apps/code-beautify/index.js

@@ -13,7 +13,7 @@ new Vue({
 
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
         chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+            let MSG_TYPE = Tarp.require('../static/js/msg_type');
             if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.CODE_BEAUTIFY) {
                 if (request.content) {
                     this.sourceContent = request.content;

+ 1 - 1
v2.0/apps/en-decode/index.js

@@ -10,7 +10,7 @@ new Vue({
     },
 
     mounted: function () {
-        let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+        let MSG_TYPE = Tarp.require('../static/js/msg_type');
 
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
         chrome.runtime.onMessage.addListener((request, sender, callback) => {

+ 235 - 0
v2.0/apps/json-format/automatic.js

@@ -0,0 +1,235 @@
+/**
+ * Json Page Automatic Format Via FeHelper
+ * @author zhaoxianlie
+ */
+let AutomaticJsonFormat = (() => {
+
+    "use strict";
+    let _getCurrAbsPath = function () {
+        let rExtractUri = /((?:http|https|file|chrome-extension):\/\/.*?\/[^:]+)(?::\d+)?:\d+/;
+        let stack;
+        try {
+            a.b();
+        }
+        catch (e) {
+            stack = e.fileName || e.sourceURL || e.stack || e.stacktrace;
+        }
+        if (stack) {
+            return rExtractUri.exec(stack)[1];
+        }
+    };
+    let absPath = _getCurrAbsPath();
+    Tarp.require.config = {
+        paths: [absPath],
+        uri: absPath
+    };
+
+    let _htmlFragment = [
+        '<div class="mod-json mod-contentscript"><div class="rst-item">',
+        '<div id="formattingMsg">',
+        '<svg id="spinner" width="16" height="16" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" version="1.1">',
+        '<path d="M 150,0 a 150,150 0 0,1 106.066,256.066 l -35.355,-35.355 a -100,-100 0 0,0 -70.711,-170.711 z" fill="#3d7fe6"></path>',
+        '</svg>加载中...',
+        '</div>',
+        '<div id="jfCallbackName_start" class="callback-name"></div>',
+        '<div id="jfContent"></div>',
+        '<pre id="jfContent_pre"></pre>',
+        '<div id="jfCallbackName_end" class="callback-name"></div>',
+        '</div></div>'
+    ].join('');
+
+    let _loadCss = function () {
+        let cssUrl = chrome.extension.getURL('json-format/without-ui.css');
+        jQuery('<link id="_fehelper_fcp_css_" href="' + cssUrl + '" rel="stylesheet" type="text/css" />').appendTo('head');
+    };
+
+    /**
+     * 从页面提取JSON文本
+     * @returns {string}
+     * @private
+     */
+    let _getJsonText = function () {
+
+        let pre = $('body>pre:eq(0)')[0] || {textContent: ""};
+        let source = $.trim(pre.textContent);
+        if (!source) {
+            source = $.trim(document.body.textContent || '')
+        }
+        if (!source) {
+            return false;
+        }
+
+        // 如果body的内容还包含HTML标签,肯定不是合法的json了
+        // 如果是合法的json,也只可能有一个text节点
+        let nodes = document.body.childNodes;
+        let newSource = '';
+        for (let i = 0, len = nodes.length; i < len; i++) {
+            if (nodes[i].nodeType === Node.TEXT_NODE) {
+                newSource += nodes[i].textContent;
+            } else if (nodes[i].nodeType === Node.ELEMENT_NODE) {
+                let tagName = nodes[i].tagName.toLowerCase();
+                let html = $.trim(nodes[i].textContent);
+                // 如果是pre标签,则看内容是不是和source一样,一样则continue
+                if (tagName === 'pre' && html === source) {
+                    continue;
+                } else if ((nodes[i].offsetWidth === 0 || nodes[i].offsetHeight === 0 || !html) && ['script', 'link'].indexOf(tagName) === -1) {
+                    // 如果用户安装迅雷或者其他的插件,也回破坏页面结构,需要兼容一下
+                    continue;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+
+        return $.trim(newSource || '') || source;
+    };
+
+    /**
+     * 此方法用于将Unicode码解码为正常字符串
+     * @param {Object} text
+     */
+    let _uniDecode = function (text) {
+        text = text.replace(/\\/g, "%").replace('%U', '%u').replace('%u0025', '%25');
+
+        text = unescape(text.toString().replace(/%2B/g, "+"));
+        let matches = text.match(/(%u00([0-9A-F]{2}))/gi);
+        if (matches) {
+            for (let matchid = 0; matchid < matches.length; matchid++) {
+                let code = matches[matchid].substring(1, 3);
+                let x = Number("0x" + code);
+                if (x >= 128) {
+                    text = text.replace(matches[matchid], code);
+                }
+            }
+        }
+        text = unescape(text.toString().replace(/%2B/g, "+"));
+
+        return text;
+    };
+
+    /**
+     * 执行format操作
+     * @private
+     */
+    let _format = function () {
+        let source = _getJsonText();
+        if (!source) {
+            return;
+        }
+
+        // JSONP形式下的callback name
+        let funcName = null;
+        // json对象
+        let jsonObj = null;
+        let newSource = source;
+        let fnTry = null;
+        let fnCatch = null;
+
+        // 下面校验给定字符串是否为一个合法的json
+        try {
+            // 再看看是不是jsonp的格式
+            let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/gm;
+            let reTry = /^(try\s*\{\s*)?/g;
+            let reCatch = /(\}\s*catch\s*\(\s*\S+\s*\)\s*\{([\s\S])*\})?$/g;
+
+            // 检测是否有try-catch包裹
+            let sourceReplaced = source.replace(reTry, function () {
+                fnTry = fnTry ? fnTry : arguments[1];
+                return '';
+            }).replace(reCatch, function () {
+                fnCatch = fnCatch ? fnCatch : arguments[1];
+                return '';
+            }).trim();
+
+            let matches = reg.exec(sourceReplaced);
+            if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) {
+                funcName = matches[1];
+                newSource = matches[2];
+                jsonObj = new Function("return " + newSource)();
+            } else {
+                reg = /^([\{\[])/;
+                if (!reg.test(source)) {
+                    return;
+                }
+            }
+
+            // 强化验证
+            if (jsonObj == null || typeof jsonObj !== 'object') {
+                jsonObj = new Function("return " + source)();
+
+                // 还要防止下面这种情况:  "{\"ret\":\"0\", \"msg\":\"ok\"}"
+                if (typeof jsonObj === "string") {
+                    // 再来一次
+                    jsonObj = new Function("return " + jsonObj)();
+                }
+            }
+        } catch (ex) {
+            return;
+        }
+
+        // 是json格式,可以进行JSON自动格式化
+        if (jsonObj != null && typeof jsonObj === "object") {
+            try {
+                // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
+                let jsonStr = JSON.stringify(jsonObj);
+                // 如果newSource的长度比原source长度短很多的话,猜测应该是格式化错了,需要撤销操作
+                // 这里一定要unicode decode一下,要不然会出现误判
+                let len1 = jsonStr.replace(/'|"|\s/g, '').length;
+                let len2 = (_uniDecode(newSource)).replace(/'|"|\s/g, '').length;
+                // 误差不允许超过20%
+                if (Math.abs(len1 - len2) / ((len1 + len2) / 2) > 0.2) {
+                    return;
+                }
+
+                newSource = jsonStr;
+            } catch (ex) {
+                // 通过JSON反解不出来的,一定有问题
+                return;
+            }
+
+            $('body').html(_htmlFragment);
+            _loadCss();
+
+            Tarp.require('./format-lib').format(newSource);
+
+            // 如果是JSONP格式的,需要把方法名也显示出来
+            if (funcName != null) {
+                if (fnTry && fnCatch) {
+                    $('#jfCallbackName_start').html('<pre style="padding:0">' + fnTry + '</pre>' + funcName + '(');
+                    $('#jfCallbackName_end').html(')<br><pre style="padding:0">' + fnCatch + '</pre>');
+                } else {
+                    $('#jfCallbackName_start').html(funcName + '(');
+                    $('#jfCallbackName_end').html(')');
+                }
+            }
+        }
+    };
+
+    let _init = function () {
+
+        $(function () {
+            let Settings = Tarp.require('../options/settings');
+            if (Settings.pageJsonMustFormat) {
+                _format();
+            } else {
+                let MSG_TYPE = Tarp.require('../static/js/msg_type');
+                chrome.extension.sendMessage({
+                    type: MSG_TYPE.GET_OPTIONS,
+                    items: [MSG_TYPE.JSON_PAGE_FORMAT]
+                }, function (opts) {
+                    if (!opts || opts.JSON_PAGE_FORMAT !== 'false') {
+                        _format();
+                    }
+                });
+            }
+        });
+    };
+
+    return {
+        init: _init
+    };
+})();
+
+AutomaticJsonFormat.init();

+ 4 - 2
v2.0/apps/json-format/format-lib.js

@@ -319,8 +319,10 @@ var JsonFormatEntrance = (function () {
 
     var _loadJquery = function () {
         if (typeof Tarp === 'object') {
-            window.jQuery = window.$ = Tarp.require('../static/vendor/jquery/jquery-3.3.1.min.js');
-            Tarp.require('../static/js/core/utils.js');
+            if (!window.jQuery && typeof jQuery !== 'function') {
+                window.jQuery = window.$ = Tarp.require('../static/vendor/jquery/jquery-3.3.1.min.js');
+            }
+            Tarp.require('../static/js/utils.js');
         } else {
             alert('无法加载Tarp.require.js');
         }

+ 1 - 1
v2.0/apps/json-format/index.js

@@ -17,7 +17,7 @@ new Vue({
 
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
         chrome.runtime.onMessage.addListener((request, sender, callback) => {
-            let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+            let MSG_TYPE = Tarp.require('../static/js/msg_type');
             if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.JSON_FORMAT) {
                 if (request.content) {
                     this.jsonSource = request.content || this.defaultResultTpl;

+ 1 - 0
v2.0/apps/json-format/without-ui.css

@@ -63,6 +63,7 @@ html {
     font-size: 14px;
     color:#333;
     direction: ltr;
+    padding-right: 10px;
 }
 html body {
     direction: inherit;

+ 23 - 1
v2.0/apps/manifest.json

@@ -40,7 +40,29 @@
         "static/img/fe-16.png",
         "static/img/fe-48.png",
         "static/img/fe-128.png",
-        "static/img/ui-icons.png"
+        "static/img/ui-icons.png",
+        "options/settings.js",
+        "static/js/utils.js",
+        "static/js/msg_type.js",
+        "json-format/without-ui.css",
+        "json-format/format-lib.js"
+    ],
+
+    "content_scripts": [
+        {
+            "matches": [
+                "http://*/*",
+                "https://*/*",
+                "file://*/*"
+            ],
+            "js": [
+                "static/vendor/jquery/jquery-3.3.1.min.js",
+                "static/vendor/require/require.js",
+                "json-format/automatic.js"
+            ],
+            "run_at": "document_end",
+            "all_frames": false
+        }
     ],
 
     "content_security_policy": "style-src 'self' 'unsafe-inline';script-src 'self' 'unsafe-eval'; object-src 'self' ",

+ 1 - 1
v2.0/apps/options/settings.js

@@ -5,7 +5,7 @@ module.exports = (() => {
 
     // 页面json格式化强制开启
     let pageJsonMustFormat = false;
-    let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+    let MSG_TYPE = Tarp.require('../static/js/msg_type');
 
     // 所有配置项
     let optionItems = [

+ 4 - 2
v2.0/apps/popup/index.js

@@ -31,7 +31,7 @@ new Vue({
             if (mType === 'COLOR_PICKER') {
                 bgPage.BgPageInstance.showColorPicker();
             } else {
-                let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+                let MSG_TYPE = Tarp.require('../static/js/msg_type');
                 bgPage.BgPageInstance.runHelper({
                     msgType: MSG_TYPE[mType],
                     useFile: useFile
@@ -39,10 +39,12 @@ new Vue({
                     if (mType === 'AJAX_DEBUGGER') {
                         bgPage.BgPageInstance.tellMeAjaxDbgSwitch((dbgSwitchOn) => {
                             this.ajaxDebugger = dbgSwitchOn ? '已开' : '已关';
-                        });
+                        }, true);
                     }
                 });
             }
+
+            window.close();
         },
 
         openOptionsPage: () => chrome.runtime.openOptionsPage()

+ 5 - 1
v2.0/apps/static/js/core/msg_type.js → v2.0/apps/static/js/msg_type.js

@@ -52,6 +52,8 @@ module.exports = {
     // 二维码解码
     QR_DECODE: 'qr-decode',
 
+    JSON_PAGE_FORMAT:'JSON_PAGE_FORMAT',
+
     //页面json代码自动格式化
     AUTO_FORMART_PAGE_JSON: "opt_item_autojson",
 
@@ -61,5 +63,7 @@ module.exports = {
     // ajax debugger
     AJAX_DEBUGGER: "ajax-debugger",
     AJAX_DEBUGGER_CONSOLE: "ajax-debugger-console",
-    AJAX_DEBUGGER_SWITCH: "ajax-debugger-switch"
+    AJAX_DEBUGGER_SWITCH: "ajax-debugger-switch",
+
+    DEV_TOOLS : 'dev-tools'
 };

+ 25 - 7
v2.0/apps/static/js/core/utils.js → v2.0/apps/static/js/utils.js

@@ -11,8 +11,8 @@ String.prototype.trim = function(){
  * @param {Object} pattern
  */
 Date.prototype.format = function(pattern){
-    var pad = function (source, length) {
-        var pre = "",
+    let pad = function (source, length) {
+        let pre = "",
             negative = (source < 0),
             string = String(Math.abs(source));
 
@@ -23,15 +23,15 @@ Date.prototype.format = function(pattern){
         return (negative ?  "-" : "") + pre + string;
     };
 
-    if ('string' != typeof pattern) {
+    if ('string' !== typeof pattern) {
         return this.toString();
     }
 
-    var replacer = function(patternPart, result) {
+    let replacer = function(patternPart, result) {
         pattern = pattern.replace(patternPart, result);
-    }
+    };
 
-    var year    = this.getFullYear(),
+    let year    = this.getFullYear(),
         month   = this.getMonth() + 1,
         date2   = this.getDate(),
         hours   = this.getHours(),
@@ -63,7 +63,7 @@ Date.prototype.format = function(pattern){
  */
 window.alert = function (content) {
     window.clearTimeout(window.feHelperAlertMsgTid);
-    var elAlertMsg = $("#fehelper_alertmsg").hide();
+    let elAlertMsg = $("#fehelper_alertmsg").hide();
     if(!elAlertMsg.get(0)) {
         elAlertMsg = $('<div id="fehelper_alertmsg" style="position:fixed;top:5px;right:5px;z-index:1000000">' +
             '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
@@ -75,4 +75,22 @@ window.alert = function (content) {
     window.feHelperAlertMsgTid = window.setTimeout(function () {
         elAlertMsg.hide(100);
     }, 3000);
+};
+
+/**
+ * 获取当前脚本的绝对路径
+ * @returns {string}
+ */
+module.exports.getCurrAbsPath = function () {
+    let rExtractUri = /((?:http|https|file|chrome-extension):\/\/.*?\/[^:]+)(?::\d+)?:\d+/;
+    let stack;
+    try {
+        a.b();
+    }
+    catch (e) {
+        stack = e.fileName || e.sourceURL || e.stack || e.stacktrace;
+    }
+    if (stack) {
+        return rExtractUri.exec(stack)[1];
+    }
 };

+ 154 - 144
v2.0/apps/static/vendor/require/require.js

@@ -19,160 +19,170 @@
 // NOTE The load parameter points to the function, which prepares the
 //      environment for each module and runs its code. Scroll down to the end of
 //      the file to see the function definition.
-(function() {
-  "use strict";
+(function () {
+    "use strict";
 
-  var cache, config, root;
-  cache = Object.create(null);
-  config = (self.TarpConfig && self.TarpConfig.require) || new Object();
-  root = {
-    children: new Array(),
-    paths: config.paths || [(new URL("./node_modules/", location.href)).href],
-    uri: location.href
-  };
+    var cache, root;
+    cache = Object.create(null);
 
-  function load(id, pwd, asyn) {
-    var matches, href, cached, request;
-    // NOTE resolve href from id
-    matches = id.match(/^((\.)?.*\/|)(.[^.]*|)(\..*|)$/);
-    href = (new URL(
-      matches[1] + matches[3] + (matches[3] && (matches[4] || ".js")),
-      matches[2] ? pwd : root.paths[0] // TODO Can we get rid of this check with more intelligent pwd setting in the engine?
-    )).href;
-    // NOTE create cache item if required
-    cached = cache[href] = cache[href] || {
-      e: undefined, // error
-      m: undefined, // module
-      p: undefined, // promise
-      r: undefined, // request
-      s: undefined, // source
-      t: undefined, // type
-      u: href, // url
-    };
-    if (!cached.p) {
-      cached.p = new Promise(function(res, rej) {
-        request = cached.r = new XMLHttpRequest();
-        request.onload = request.onerror = request.ontimeout = function() {
-          var tmp, done, pattern, match, loading = 0;
-          // `request` might have been changed by line 60ff
-          if (request = cached.r) {
-            cached.r = null;
-            if ((request.status > 99) && ((href = request.responseURL) != cached.u)) {
-              if (cache[href]) {
-                cached = cache[cached.u] = cache[href];
-                cached.p.then(res, rej);
-                // NOTE Replace pending request of actual module with the already completed request and abort the
-                //      pending request.
-                if (cached.r) {
-                  tmp = cached.r;
-                  cached.r = request;
-                  tmp.abort();
-                  tmp.onload();
-                }
-                return;
-              }
-              else {
-                cache[href] = cached;
-              }
-            }
-            if ((request.status > 99) && (request.status < 400)) {
-              cached.s = request.responseText;
-              cached.t = request.getResponseHeader("Content-Type");
-              done = function() { if (--loading < 0) res(cached); };
-              // NOTE Pre-load submodules if the request is asynchronous (timeout > 0).
-              if (request.timeout) {
-                // TODO Write a real parser that returns all modules that are preloadable
-                pattern = /require(?:\.resolve)?\((?:"((?:[^"\\]|\\.)+)"|'((?:[^'\\]|\\.)+)')\)/g;
-                while((match = pattern.exec(cached.s)) !== null) {
-                  // NOTE Only add modules to the loading-queue that are still pending
-                  if ((tmp = load(match[1]||match[2], href, true)).r) {
-                    loading++;
-                    tmp.p.then(done, done);
-                  }
-                }
-              }
-              done();
-            }
-            else {
-              rej(cached.e = new Error(href + " " + request.status));
-            }
-          }
+    function getRoot() {
+        var config = (self.Tarp && self.Tarp.require && self.Tarp.require.config) || {};
+        root = {
+            children: [],
+            paths: config.paths || [(new URL("./node_modules/", location.href)).href],
+            uri: config.uri || location.href
         };
-      });
+        return root;
     }
-    // NOTE `request` is only defined if the module is requested for the first time.
-    if (request = request || (!asyn && cached.r)) {
-      try {
-        request.abort();
-        request.timeout = asyn ? 10000 : 0;
-        request.open("GET", href, asyn);
-        request.send();
-      }
-      catch (e) {
-        request.onerror();
-      }
+
+    function load(id, pwd, asyn) {
+        var matches, href, cached, request;
+        // NOTE resolve href from id
+        matches = id.match(/^((\.)?.*\/|)(.[^.]*|)(\..*|)$/);
+        href = (new URL(
+            matches[1] + matches[3] + (matches[3] && (matches[4] || ".js")),
+            matches[2] ? pwd : root.paths[0] // TODO Can we get rid of this check with more intelligent pwd setting in the engine?
+        )).href;
+
+        // NOTE create cache item if required
+        cached = cache[href] = cache[href] || {
+            e: undefined, // error
+            m: undefined, // module
+            p: undefined, // promise
+            r: undefined, // request
+            s: undefined, // source
+            t: undefined, // type
+            u: href, // url
+        };
+        if (!cached.p) {
+            cached.p = new Promise(function (res, rej) {
+                request = cached.r = new XMLHttpRequest();
+                request.onload = request.onerror = request.ontimeout = function () {
+                    var tmp, done, pattern, match, loading = 0;
+                    // `request` might have been changed by line 60ff
+                    if (request = cached.r) {
+                        cached.r = null;
+                        if ((request.status > 99) && ((href = request.responseURL) != cached.u)) {
+                            if (cache[href]) {
+                                cached = cache[cached.u] = cache[href];
+                                cached.p.then(res, rej);
+                                // NOTE Replace pending request of actual module with the already completed request and abort the
+                                //      pending request.
+                                if (cached.r) {
+                                    tmp = cached.r;
+                                    cached.r = request;
+                                    tmp.abort();
+                                    tmp.onload();
+                                }
+                                return;
+                            }
+                            else {
+                                cache[href] = cached;
+                            }
+                        }
+                        if ((request.status > 99) && (request.status < 400)) {
+                            cached.s = request.responseText;
+                            cached.t = request.getResponseHeader("Content-Type");
+                            done = function () {
+                                if (--loading < 0) res(cached);
+                            };
+                            // NOTE Pre-load submodules if the request is asynchronous (timeout > 0).
+                            if (request.timeout) {
+                                // TODO Write a real parser that returns all modules that are preloadable
+                                pattern = /require(?:\.resolve)?\((?:"((?:[^"\\]|\\.)+)"|'((?:[^'\\]|\\.)+)')\)/g;
+                                while ((match = pattern.exec(cached.s)) !== null) {
+                                    // NOTE Only add modules to the loading-queue that are still pending
+                                    if ((tmp = load(match[1] || match[2], href, true)).r) {
+                                        loading++;
+                                        tmp.p.then(done, done);
+                                    }
+                                }
+                            }
+                            done();
+                        }
+                        else {
+                            rej(cached.e = new Error(href + " " + request.status));
+                        }
+                    }
+                };
+            });
+        }
+        // NOTE `request` is only defined if the module is requested for the first time.
+        if (request = request || (!asyn && cached.r)) {
+            try {
+                request.abort();
+                request.timeout = asyn ? 10000 : 0;
+                request.open("GET", href, asyn);
+                request.send();
+            }
+            catch (e) {
+                request.onerror();
+            }
+        }
+        if (cached.e)
+            throw cached.e;
+        return cached;
     }
-    if (cached.e)
-      throw cached.e;
-    return cached;
-  }
 
-  function evaluate(cached, parent) {
-    var module;
-    if (!cached.m) {
-      module = cached.m = {
-        children: new Array(),
-        exports: Object.create(null),
-        filename: cached.u,
-        id: cached.u,
-        loaded: false,
-        parent: parent,
-        paths: parent.paths.slice(),
-        require: undefined,
-        uri: cached.u
-      },
-      module.require = factory(module);
-      parent.children.push(module);
-      if (cached.t == "application/json")
-        module.exports = JSON.parse(cached.s);
-      else
-        (new Function(
-          "exports,require,module,__filename,__dirname",
-          cached.s + "\n//# sourceURL=" + module.uri
-        ))(module.exports, module.require, module, module.uri, module.uri.match(/.*\//)[0]);
-      module.loaded = true;
+    function evaluate(cached, parent) {
+        var module;
+        if (!cached.m) {
+            module = cached.m = {
+                children: new Array(),
+                exports: Object.create(null),
+                filename: cached.u,
+                id: cached.u,
+                loaded: false,
+                parent: parent,
+                paths: parent.paths.slice(),
+                require: undefined,
+                uri: cached.u
+            },
+                module.require = factory(module);
+            parent.children.push(module);
+            if (cached.t == "application/json")
+                module.exports = JSON.parse(cached.s);
+            else
+                (new Function(
+                    "exports,require,module,__filename,__dirname",
+                    cached.s + "\n//# sourceURL=" + module.uri
+                ))(module.exports, module.require, module, module.uri, module.uri.match(/.*\//)[0]);
+            module.loaded = true;
+        }
+        return cached.m;
     }
-    return cached.m;
-  }
 
-  function factory(parent) {
-    function requireEngine(mode, id, asyn) {
-      function afterLoad(cached) {
-        var regex = /package\.json$/;
-        if (regex.test(cached.u) && !regex.test(id)) {
-          parent = evaluate(cached, parent);
-          return requireEngine(mode, parent.exports.main, asyn);
+    function factory(parent) {
+        function requireEngine(mode, id, asyn) {
+
+            parent = parent === -1 ? getRoot() : parent;
+
+            function afterLoad(cached) {
+                var regex = /package\.json$/;
+                if (regex.test(cached.u) && !regex.test(id)) {
+                    parent = evaluate(cached, parent);
+                    return requireEngine(mode, parent.exports.main, asyn);
+                }
+                else if (mode == 1)
+                    return cached.u;
+                else if (mode == 2)
+                    return [id[0] == "." ? parent.uri.match(/.*\//)[0] : root.uri]; // TODO Can this be cleaned up?
+                else
+                    return evaluate(cached, parent).exports;
+            }
+
+            return asyn ?
+                new Promise(function (res, rej) {
+                    load(id, parent.uri, asyn).p.then(afterLoad).then(res, rej);
+                }) :
+                afterLoad(load(id, parent.uri, asyn));
         }
-        else if (mode == 1)
-          return cached.u;
-        else if (mode == 2)
-          return [id[0] == "." ? parent.uri.match(/.*\//)[0] : root.uri]; // TODO Can this be cleaned up?
-        else
-          return evaluate(cached, parent).exports;
-      }
 
-      return asyn ?
-        new Promise(function(res, rej) {
-          load(id, parent.uri, asyn).p.then(afterLoad).then(res, rej);
-        }):
-        afterLoad(load(id, parent.uri, asyn));
+        var require = requireEngine.bind(undefined, 0);
+        require.resolve = requireEngine.bind(require, 1);
+        require.resolve.paths = requireEngine.bind(require.resolve, 2);
+        return require;
     }
 
-    var require = requireEngine.bind(undefined, 0);
-    require.resolve = requireEngine.bind(require, 1);
-    require.resolve.paths = requireEngine.bind(require.resolve, 2);
-    return require;
-  }
-
-  (self.Tarp = self.Tarp || {}).require = factory(root);
+    (self.Tarp = self.Tarp || {}).require = factory(-1);
 })();