From 978837a3e250acad068ff78e940a9638f47c04bf Mon Sep 17 00:00:00 2001 From: Bo Borgerson Date: Thu, 26 Jan 2017 11:52:36 -0800 Subject: [PATCH 1/5] Add new package: flab Future LABjs --- packages/flab/LAB.src.js | 514 ++++++++++++++++++ packages/flab/index.js | 4 + packages/flab/minify.js | 12 + packages/flab/package.json | 38 ++ packages/flab/stringify.js | 9 + .../react-server/core/renderMiddleware.js | 4 +- packages/react-server/core/util/LABString.js | 11 - packages/react-server/package.json | 1 + 8 files changed, 580 insertions(+), 13 deletions(-) create mode 100644 packages/flab/LAB.src.js create mode 100644 packages/flab/index.js create mode 100644 packages/flab/minify.js create mode 100644 packages/flab/package.json create mode 100644 packages/flab/stringify.js delete mode 100644 packages/react-server/core/util/LABString.js diff --git a/packages/flab/LAB.src.js b/packages/flab/LAB.src.js new file mode 100644 index 000000000..99807cd22 --- /dev/null +++ b/packages/flab/LAB.src.js @@ -0,0 +1,514 @@ +/*! LAB.js (LABjs :: Loading And Blocking JavaScript) + v2.0.3 (c) Kyle Simpson + MIT License +*/ + +(function(global){ + var _$LAB = global.$LAB, + + // constants for the valid keys of the options object + _UseLocalXHR = "UseLocalXHR", + _AlwaysPreserveOrder = "AlwaysPreserveOrder", + _AllowDuplicates = "AllowDuplicates", + _CacheBust = "CacheBust", + /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ + _BasePath = "BasePath", + + // stateless variables used across all $LAB instances + root_page = /^[^?#]*\//.exec(location.href)[0], + root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], + append_to = document.head || document.getElementsByTagName("head"), + + // inferences... ick, but still necessary + opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), + +/*!START_DEBUG*/ + // console.log() and console.error() wrappers + log_msg = function(){}, + log_error = log_msg, +/*!END_DEBUG*/ + + // feature sniffs (yay!) + test_script_elem = document.createElement("script"), + explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 + real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? + script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order + + // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) + xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko + ; + +/*!START_DEBUG*/ + // define console wrapper functions if applicable + if (global.console && global.console.log) { + if (!global.console.error) global.console.error = global.console.log; + log_msg = function(msg) { global.console.log(msg); }; + log_error = function(msg,err) { global.console.error(msg,err); }; + } +/*!END_DEBUG*/ + + // test for function + function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; } + + // test for array + function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } + + // make script URL absolute/canonical + function canonical_uri(src,base_path) { + var absolute_regex = /^\w+\:\/\//; + + // is `src` is protocol-relative (begins with // or ///), prepend protocol + if (/^\/\/\/?/.test(src)) { + src = location.protocol + src; + } + // is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /) + else if (!absolute_regex.test(src) && src.charAt(0) != "/") { + // prepend `base_path`, if any + src = (base_path || "") + src; + } + // make sure to return `src` as absolute + return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src); + } + + // merge `source` into `target` + function merge_objs(source,target) { + for (var k in source) { if (source.hasOwnProperty(k)) { + target[k] = source[k]; // TODO: does this need to be recursive for our purposes? + }} + return target; + } + + // does the chain group have any ready-to-execute scripts? + function check_chain_group_scripts_ready(chain_group) { + var any_scripts_ready = false; + for (var i=0; i 0) { + for (var i=0; i=0;) { + val = queue.shift(); + $L = $L[val.type].apply(null,val.args); + } + return $L; + }, + + // rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB + noConflict:function(){ + global.$LAB = _$LAB; + return instanceAPI; + }, + + // create another clean instance of $LAB + sandbox:function(){ + return create_sandbox(); + } + }; + + return instanceAPI; + } + + // create the main instance of $LAB + global.$LAB = create_sandbox(); + + + /* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html + NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). + + The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does + proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked + document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. + For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or + fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. + */ + (function(addEvent,domLoaded,handler){ + if (document.readyState == null && document[addEvent]){ + document.readyState = "loading"; + document[addEvent](domLoaded,handler = function(){ + document.removeEventListener(domLoaded,handler,false); + document.readyState = "complete"; + },false); + } + })("addEventListener","DOMContentLoaded"); + +})(this); \ No newline at end of file diff --git a/packages/flab/index.js b/packages/flab/index.js new file mode 100644 index 000000000..e80f71bb9 --- /dev/null +++ b/packages/flab/index.js @@ -0,0 +1,4 @@ +module.exports = { + src: require("./string/src"), + min: require("./string/min"), +} diff --git a/packages/flab/minify.js b/packages/flab/minify.js new file mode 100644 index 000000000..656b6aef2 --- /dev/null +++ b/packages/flab/minify.js @@ -0,0 +1,12 @@ +console.log( + "\n"+ + "/*! LAB.js (LABjs :: Loading And Blocking JavaScript)\n"+ + " v2.0.3 (c) Kyle Simpson\n"+ + " MIT License\n"+ + "*/\n"+ + require("uglify-js").minify( + require("fs").readFileSync("/dev/stdin", "utf-8") + .replace(/\/\*!START_DEBUG(?:.|[\n\r])*?END_DEBUG\*\//g, ""), + {fromString: true} + ).code +); diff --git a/packages/flab/package.json b/packages/flab/package.json new file mode 100644 index 000000000..fa99d806d --- /dev/null +++ b/packages/flab/package.json @@ -0,0 +1,38 @@ +{ + "name": "flab", + "version": "0.0.1", + "description": "Future Loading and Blocking JS", + "main": "index.js", + "files": [ + "index.js", + "string" + ], + "scripts": { + "prepublish": "mkdir -p string && npm run build-src && npm run build-min", + "build-src": "node stringify.js < LAB.src.js > string/src.js", + "build-min": "node minify.js < LAB.src.js | node stringify.js > string/min.js", + "clean": "rimraf string", + "test": "echo \"No tests yet...\"" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/redfin/react-server.git" + }, + "keywords": [ + "loading", + "blocking", + "js", + "asynchronous", + "loader" + ], + "author": "Bo Borgerson", + "license": "MIT", + "bugs": { + "url": "https://github.com/redfin/react-server/issues" + }, + "homepage": "https://github.com/redfin/react-server#readme", + "devDependencies": { + "rimraf": "^2.5.4", + "uglify-js": "^2.7.5" + } +} diff --git a/packages/flab/stringify.js b/packages/flab/stringify.js new file mode 100644 index 000000000..375b9bed8 --- /dev/null +++ b/packages/flab/stringify.js @@ -0,0 +1,9 @@ +console.log( + 'module.exports = '+ + require("fs").readFileSync("/dev/stdin", "utf-8") + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/^/gm, '"') + .replace(/$/gm, '\\n"+')+ + '"";' +); diff --git a/packages/react-server/core/renderMiddleware.js b/packages/react-server/core/renderMiddleware.js index aa9814f06..921841701 100644 --- a/packages/react-server/core/renderMiddleware.js +++ b/packages/react-server/core/renderMiddleware.js @@ -6,7 +6,7 @@ var logger = require('./logging').getLogger(__LOGGER__), RequestContext = require('./context/RequestContext'), RequestLocalStorage = require('./util/RequestLocalStorage'), RLS = RequestLocalStorage.getNamespace(), - LABString = require('./util/LABString'), + flab = require('flab'), Q = require('q'), config = require('./config'), ExpressServerRequest = require("./ExpressServerRequest"), @@ -484,7 +484,7 @@ function renderScriptsAsync(scripts, res) { if (!RLS().didLoadLAB){ // This is the full implementation of LABjs. - res.write(LABString); + res.write(flab.min); // We always want scripts to be executed in order. res.write("$LAB.setGlobalDefaults({AlwaysPreserveOrder:true});"); diff --git a/packages/react-server/core/util/LABString.js b/packages/react-server/core/util/LABString.js deleted file mode 100644 index c10a68527..000000000 --- a/packages/react-server/core/util/LABString.js +++ /dev/null @@ -1,11 +0,0 @@ -// Careful: We've got a local patch here! -// -// We've added support for a `crossOrigin` parameter. -// -module.exports = ` -/*! LAB.js (LABjs :: Loading And Blocking JavaScript) - v2.0.3 (c) Kyle Simpson - MIT License -*/ -(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\\//.exec(location.href)[0],D=/^\\w+\\:\\/\\/\\/?[^\\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\\w+\\:\\/\\//;if(/^\\/\\/\\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this); -`; diff --git a/packages/react-server/package.json b/packages/react-server/package.json index 961ce9abe..374e7dd75 100644 --- a/packages/react-server/package.json +++ b/packages/react-server/package.json @@ -29,6 +29,7 @@ "cookie-parser": "1.4.3", "eventemitter3": "^2.0.2", "express-state": "^1.4.0", + "flab": "^0.0.1", "lodash": "^4.16.4", "mobile-detect": "^1.3.5", "q": "1.4.1", From 08b3372866f18b35df5482b054c61724a03296b3 Mon Sep 17 00:00:00 2001 From: Bo Borgerson Date: Thu, 26 Jan 2017 15:59:45 -0800 Subject: [PATCH 2/5] Default debug to true The debug code gets totally removed in the minified build. --- packages/flab/LAB.src.js | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/flab/LAB.src.js b/packages/flab/LAB.src.js index 99807cd22..42045c6ac 100644 --- a/packages/flab/LAB.src.js +++ b/packages/flab/LAB.src.js @@ -5,7 +5,7 @@ (function(global){ var _$LAB = global.$LAB, - + // constants for the valid keys of the options object _UseLocalXHR = "UseLocalXHR", _AlwaysPreserveOrder = "AlwaysPreserveOrder", @@ -13,27 +13,27 @@ _CacheBust = "CacheBust", /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ _BasePath = "BasePath", - + // stateless variables used across all $LAB instances root_page = /^[^?#]*\//.exec(location.href)[0], root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], append_to = document.head || document.getElementsByTagName("head"), - + // inferences... ick, but still necessary opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), /*!START_DEBUG*/ // console.log() and console.error() wrappers - log_msg = function(){}, + log_msg = function(){}, log_error = log_msg, /*!END_DEBUG*/ - + // feature sniffs (yay!) test_script_elem = document.createElement("script"), explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order - + // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko ; @@ -56,7 +56,7 @@ // make script URL absolute/canonical function canonical_uri(src,base_path) { var absolute_regex = /^\w+\:\/\//; - + // is `src` is protocol-relative (begins with // or ///), prepend protocol if (/^\/\/\/?/.test(src)) { src = location.protocol + src; @@ -115,7 +115,7 @@ // setTimeout() "yielding" prevents some weird race/crash conditions in older browsers setTimeout(function(){ var script, src = script_obj.real_src, xhr; - + // don't proceed until `append_to` is ready to append to if ("item" in append_to) { // check if `append_to` ref is still a live node list if (!append_to[0]) { // `append_to` node not yet ready @@ -129,7 +129,7 @@ script = document.createElement("script"); if (script_obj.type) script.type = script_obj.type; if (script_obj.charset) script.charset = script_obj.charset; - + // should preloading be used for this script? if (preload_this_script) { // real script preloading? @@ -191,7 +191,7 @@ } },0); } - + // create a clean instance of $LAB function create_sandbox() { var global_defaults = {}, @@ -200,34 +200,34 @@ registry = {}, instanceAPI ; - + // global defaults global_defaults[_UseLocalXHR] = true; global_defaults[_AlwaysPreserveOrder] = false; global_defaults[_AllowDuplicates] = false; global_defaults[_CacheBust] = false; - /*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/ + /*!START_DEBUG*/global_defaults[_Debug] = true;/*!END_DEBUG*/ global_defaults[_BasePath] = ""; // execute a script that has been preloaded already function execute_preloaded_script(chain_opts,script_obj,registry_item) { var script; - + function preload_execute_finished() { if (script != null) { // make sure this only ever fires once script = null; script_executed(registry_item); } } - + if (registry[script_obj.src].finished) return; if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true; - + script = registry_item.elem || document.createElement("script"); if (script_obj.type) script.type = script_obj.type; if (script_obj.charset) script.charset = script_obj.charset; create_script_load_listener(script,registry_item,"finished",preload_execute_finished); - + // script elem was real-preloaded if (registry_item.elem) { registry_item.elem = null; @@ -248,7 +248,7 @@ preload_execute_finished(); } } - + // process the script request setup function do_script(chain_opts,script_obj,chain_group,preload_this_script) { var registry_item, @@ -256,13 +256,13 @@ ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); }, finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); } ; - + script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]); - script_obj.real_src = script_obj.src + + script_obj.real_src = script_obj.src + // append cache-bust param to URL? (chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "") ; - + if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false}; registry_items = registry[script_obj.src].items; @@ -312,7 +312,7 @@ scripts_currently_loading = false, group ; - + // called when a script has finished preloading function chain_script_ready(script_obj,exec_trigger) { /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/ @@ -357,7 +357,7 @@ group = false; } } - + // setup next chain script group function init_script_chain_group() { if (!group || !group.scripts) { @@ -372,14 +372,14 @@ for (var i=0; i Date: Thu, 26 Jan 2017 16:02:07 -0800 Subject: [PATCH 3/5] Add crossOrigin support back in --- packages/flab/LAB.src.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/flab/LAB.src.js b/packages/flab/LAB.src.js index 42045c6ac..e0a0874f1 100644 --- a/packages/flab/LAB.src.js +++ b/packages/flab/LAB.src.js @@ -129,6 +129,7 @@ script = document.createElement("script"); if (script_obj.type) script.type = script_obj.type; if (script_obj.charset) script.charset = script_obj.charset; + if (script_obj.crossOrigin) script.crossOrigin = script_obj.crossOrigin; // should preloading be used for this script? if (preload_this_script) { @@ -226,6 +227,7 @@ script = registry_item.elem || document.createElement("script"); if (script_obj.type) script.type = script_obj.type; if (script_obj.charset) script.charset = script_obj.charset; + if (script_obj.crossOrigin) script.crossOrigin = script_obj.crossOrigin; create_script_load_listener(script,registry_item,"finished",preload_execute_finished); // script elem was real-preloaded From 0cbaab80089825ccd64287e04c795bd83b3c63ce Mon Sep 17 00:00:00 2001 From: Bo Borgerson Date: Thu, 26 Jan 2017 16:05:53 -0800 Subject: [PATCH 4/5] Add a basic README --- packages/flab/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/flab/README.md diff --git a/packages/flab/README.md b/packages/flab/README.md new file mode 100644 index 000000000..f2c641d3d --- /dev/null +++ b/packages/flab/README.md @@ -0,0 +1,5 @@ +Future Loading and Blocking JS + +This is based on the awesome [LABjs](https://github.com/getify/LABjs) package. + +TODO: Flesh out this README. :grin: From 62586864f72e82fe800c5eb16258535963a94f7d Mon Sep 17 00:00:00 2001 From: Bo Borgerson Date: Fri, 27 Jan 2017 11:43:29 -0800 Subject: [PATCH 5/5] Portabled stdin slurp --- packages/flab/minify.js | 7 +++---- packages/flab/package.json | 1 + packages/flab/stringify.js | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/flab/minify.js b/packages/flab/minify.js index 656b6aef2..984bd8dae 100644 --- a/packages/flab/minify.js +++ b/packages/flab/minify.js @@ -1,12 +1,11 @@ -console.log( +require("get-stdin")().then(src => console.log( "\n"+ "/*! LAB.js (LABjs :: Loading And Blocking JavaScript)\n"+ " v2.0.3 (c) Kyle Simpson\n"+ " MIT License\n"+ "*/\n"+ require("uglify-js").minify( - require("fs").readFileSync("/dev/stdin", "utf-8") - .replace(/\/\*!START_DEBUG(?:.|[\n\r])*?END_DEBUG\*\//g, ""), + src.replace(/\/\*!START_DEBUG(?:.|[\n\r])*?END_DEBUG\*\//g, ""), {fromString: true} ).code -); +)); diff --git a/packages/flab/package.json b/packages/flab/package.json index fa99d806d..36ed8b609 100644 --- a/packages/flab/package.json +++ b/packages/flab/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://github.com/redfin/react-server#readme", "devDependencies": { + "get-stdin": "^5.0.1", "rimraf": "^2.5.4", "uglify-js": "^2.7.5" } diff --git a/packages/flab/stringify.js b/packages/flab/stringify.js index 375b9bed8..864dc0b26 100644 --- a/packages/flab/stringify.js +++ b/packages/flab/stringify.js @@ -1,9 +1,8 @@ -console.log( - 'module.exports = '+ - require("fs").readFileSync("/dev/stdin", "utf-8") +require("get-stdin")().then(src => console.log( + 'module.exports = ' + src .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/^/gm, '"') .replace(/$/gm, '\\n"+')+ '"";' -); +));