├── .gitignore ├── test ├── index2.js ├── index1.js └── script-loader-test.html ├── README.md ├── package.json ├── script-loader-angular.js ├── script-loader-promise.js └── script-loader.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) -------------------------------------------------------------------------------- /test/index2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2015-12-14 5 | */ 6 | ;(function () { 7 | 8 | 'use strict'; 9 | 10 | console.log('index2'); 11 | 12 | var div = document.createElement('div'); 13 | div.textContent = 'index2'; 14 | 15 | document.body.appendChild(div); 16 | 17 | })(); -------------------------------------------------------------------------------- /test/index1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2015-12-14 5 | */ 6 | ;(function () { 7 | 8 | 'use strict'; 9 | 10 | 11 | var now = Date.now(); 12 | 13 | while (Date.now() - now < 1500) { 14 | 15 | } 16 | console.log('index1'); 17 | 18 | var div = document.createElement('div'); 19 | div.textContent = 'index1'; 20 | 21 | document.body.appendChild(div); 22 | 23 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # script-loader 2 | 脚本加载器,包括同步、异步、延时等方式,支持promise 3 | 4 | * script-loader 基于原生js的loader,无其他依赖 5 | * script-loader-promise 基于es6 Promise的promise loader 6 | * script-loader-angular 基于angular $q的promise loader 7 | 8 | ```js 9 | 10 | ScriptLoader.loadAsync('index1.js', 'index2.js', function(){ 11 | console.log('loaded'); 12 | }); 13 | 14 | ``` 15 | 16 | install 17 | 18 | ```shell 19 | npm install browser-script-loader --save 20 | ``` 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-script-loader", 3 | "version": "3.0.4", 4 | "description": "a simple & lightweight script loader,provide sync & async way to load script", 5 | "main": "script-loader.js", 6 | "keywords": [ 7 | "script loader" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/kuitos/script-loader.git" 15 | }, 16 | "author": "Kuitos Lau", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/kuitos/script-loader/issues" 20 | }, 21 | "homepage": "https://github.com/kuitos/script-loader#readme" 22 | } 23 | -------------------------------------------------------------------------------- /test/script-loader-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | script-loader-test 6 | 7 | 8 | 9 | 10 | 11 | 12 |
hello world
13 | 14 | 15 | 16 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /script-loader-angular.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2015-09-14 5 | */ 6 | ; 7 | (function (angular, undefined) { 8 | 9 | 'use strict'; 10 | 11 | angular 12 | .module('ngUtils.scriptLoader', ['ng']) 13 | .factory('$scriptLoader', ScriptLoaderConstructor); 14 | 15 | ScriptLoaderConstructor.$inject = ['$q']; 16 | function ScriptLoaderConstructor($q) { 17 | 18 | // dynamically add base tag as well as css and javascript files. 19 | // we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources 20 | // before the base attribute is added, causing 404 and terribly slow loading of the docs app. 21 | function outerHTML(node) { 22 | // if IE, Chrome take the internal method otherwise build one 23 | return node.outerHTML || (function (n) { 24 | var div = document.createElement('div'), h; 25 | div.appendChild(n); 26 | h = div.innerHTML; 27 | div = null; 28 | return h; 29 | })(node); 30 | } 31 | 32 | function createScriptDom(scriptSrc) { 33 | var scriptDom = document.createElement("script"); 34 | // 如果以http://或https://开头,则使用原始路径 35 | scriptDom.src = ~scriptSrc.search(/^((http|https):\/\/)/g) ? scriptSrc : (ScriptLoader.BASE_PATH + scriptSrc); 36 | 37 | return scriptDom; 38 | } 39 | 40 | var headEl = document.getElementsByTagName('head')[0], 41 | scriptsCache = []; // 已加载过的脚本缓存 42 | 43 | var ScriptLoader = { 44 | 45 | BASE_PATH: "", 46 | 47 | /** 48 | * 同步方式加载script,这种方式加载多个脚本浏览器会并行下载且按标签顺序执行,但是会阻塞后续其他资源(即后面script标签里js代码的执行) 49 | * @param scriptList 同步加载脚本列表 50 | */ 51 | loadScriptsSync: function (scriptList) { 52 | 53 | scriptList.forEach(function (scriptSrc) { 54 | 55 | // 当前脚本第一次加载 56 | if (!~scriptsCache.indexOf(scriptSrc)) { 57 | 58 | document.write(outerHTML(createScriptDom(scriptSrc))); 59 | scriptsCache.push(scriptSrc); 60 | } 61 | 62 | }); 63 | }, 64 | 65 | /** 66 | * 异步方式加载script,这种方式加载多个脚本浏览器会并行下载且一下载完成就执行(执行顺序依据下载完成时间,所以顺序不定),但是不会阻塞后续其他资源下载 67 | * @param scriptList 需要加载的文件列表 68 | * @param loadedCallback 脚本队列执行完之后执行的回调 69 | */ 70 | loadScriptsAsync: function (scriptList, loadedCallback) { 71 | 72 | function addCallbackWhenScriptLoaded(script, resolve, func) { 73 | 74 | script.onloadDone = false; 75 | 76 | // 脚本执行完成之后执行的回调,onreadystatechange在IE中有效,onload在其他浏览器中有效 77 | script.onload = function () { 78 | resolve(); 79 | // helpful for gc 80 | script = null; 81 | }; 82 | script.onreadystatechange = function () { 83 | 84 | if (("loaded" === script.readyState || "complete" === script.readyState) && !script.onloadDone) { 85 | script.onloadDone = true; 86 | script.onload(); 87 | } 88 | }; 89 | 90 | } 91 | 92 | var scriptPromises; 93 | 94 | if (!scriptList || !scriptList.length) { 95 | scriptPromises = $q.resolve(); 96 | } else { 97 | scriptPromises = scriptList.map(function (scriptSrc) { 98 | 99 | return $q(function (resolve) { 100 | 101 | var scriptDom; 102 | // 脚本第一次加载 103 | if (!~scriptsCache.indexOf(scriptSrc)) { 104 | 105 | scriptDom = createScriptDom(scriptSrc); 106 | addCallbackWhenScriptLoaded(scriptDom, resolve); 107 | headEl.appendChild(scriptDom); 108 | 109 | scriptsCache.push(scriptSrc); 110 | 111 | } else { 112 | resolve(); 113 | } 114 | 115 | }); 116 | 117 | }); 118 | } 119 | 120 | return $q.all(scriptPromises).then(loadedCallback || function noop(promises) { 121 | return promises; 122 | }); 123 | 124 | }, 125 | 126 | /** 127 | * 为最大提高网站加载速度,将不会立即用到的资源延时加载,避免跟需要立即执行的脚本抢占线程 128 | * @param scriptList 129 | * @param loadedCallback 130 | */ 131 | loadScriptAsyncDelayed: function (scriptList, loadedCallback) { 132 | 133 | return $q(function (resolve) { 134 | 135 | window.setTimeout(function () { 136 | resolve(ScriptLoader.loadScriptsAsync(scriptList, loadedCallback)); 137 | }, 500); 138 | }); 139 | 140 | } 141 | }; 142 | 143 | return ScriptLoader; 144 | 145 | } 146 | 147 | })(window.angular); 148 | -------------------------------------------------------------------------------- /script-loader-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2015-09-14 5 | */ 6 | (function (window) { 7 | "use strict"; 8 | 9 | // dynamically add base tag as well as css and javascript files. 10 | // we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources 11 | // before the base attribute is added, causing 404 and terribly slow loading of the docs app. 12 | function outerHTML(node) { 13 | // if IE, Chrome take the internal method otherwise build one 14 | return node.outerHTML || (function (n) { 15 | var div = document.createElement('div'), h; 16 | div.appendChild(n); 17 | h = div.innerHTML; 18 | div = null; 19 | return h; 20 | })(node); 21 | } 22 | 23 | function creteScriptDom(scriptSrc) { 24 | var scriptDom = document.createElement("script"); 25 | // 如果以http://或https://开头,则使用原始路径 26 | scriptDom.src = ~scriptSrc.search(/^((http|https):\/\/)/g) ? scriptSrc : (ScriptLoader.BASE_PATH + scriptSrc); 27 | 28 | return scriptDom; 29 | } 30 | 31 | var headEl = document.getElementsByTagName('head')[0], 32 | loadedScripts = [], // 已加载过的脚本缓存 33 | slice = Array.prototype.slice; 34 | 35 | var ScriptLoader = { 36 | 37 | BASE_PATH: "", 38 | 39 | /** 40 | * 同步方式加载script,这种方式加载多个脚本浏览器会并行下载且按标签顺序执行,但是会阻塞后续其他资源(即后面script标签里js代码的执行) 41 | * @param scriptList 同步加载脚本列表 42 | */ 43 | loadScriptsSync: function (scriptList) { 44 | 45 | scriptList.forEach(function (scriptSrc) { 46 | 47 | // 当前脚本第一次加载 48 | if (!~loadedScripts.indexOf(scriptSrc)) { 49 | 50 | document.write(outerHTML(creteScriptDom(scriptSrc))); 51 | 52 | loadedScripts.push(scriptSrc); 53 | } 54 | 55 | }); 56 | }, 57 | 58 | /** 59 | * 异步方式加载script,这种方式加载多个脚本浏览器会并行下载且一下载完成就执行(执行顺序依据下载完成时间,所以顺序不定),但是不会阻塞后续其他资源下载 60 | * @param scriptList 需要加载的文件列表 61 | * @param loadedCallback 脚本队列执行完之后执行的回调 62 | */ 63 | loadScriptsAsync: function (scriptList, loadedCallback) { 64 | 65 | var scriptList = slice.call(arguments), 66 | loadedCallback = scriptList[scriptList.length - 1]; 67 | 68 | if (typeof loadedCallback === 'function') { 69 | scriptList.pop(); 70 | } 71 | 72 | function addCallbackWhenScriptLoaded(script, resolve) { 73 | 74 | script.onloadDone = false; 75 | 76 | // 脚本执行完成之后执行的回调,onreadystatechange在IE中有效,onload在其他浏览器中有效 77 | script.onload = function () { 78 | resolve(); 79 | // helpful for gc 80 | script = null; 81 | }; 82 | script.onreadystatechange = function () { 83 | 84 | if (("loaded" === script.readyState || "complete" === script.readyState) && !script.onloadDone) { 85 | script.onloadDone = true; 86 | script.onload(); 87 | } 88 | }; 89 | 90 | } 91 | 92 | var promises = scriptList.map(function (scriptSrc) { 93 | 94 | return new Promise(function (resolve) { 95 | 96 | var scriptDom; 97 | 98 | // 脚本第一次加载 99 | if (!~loadedScripts.indexOf(scriptSrc)) { 100 | 101 | scriptDom = creteScriptDom(scriptSrc); 102 | addCallbackWhenScriptLoaded(scriptDom, resolve); 103 | headEl.appendChild(scriptDom); 104 | 105 | loadedScripts.push(scriptSrc); 106 | 107 | } else { 108 | resolve(); 109 | } 110 | 111 | }); 112 | 113 | }); 114 | 115 | return Promise.all(promises).then(loadedCallback || function noop(promises) { return promises }); 116 | 117 | }, 118 | 119 | /** 120 | * 为最大提高网站加载速度,将不会立即用到的资源延时加载,避免跟需要立即执行的脚本抢占线程 121 | * @param scriptList 122 | * @param loadedCallback 123 | */ 124 | loadScriptAsyncDelayed: function (scriptList, loadedCallback) { 125 | 126 | var args = slice.call(arguments); 127 | 128 | return new Promise(function (resolve) { 129 | 130 | window.setTimeout(function () { 131 | resolve(ScriptLoader.loadScriptsAsync.apply(ScriptLoader, args)); 132 | }, 500); 133 | }); 134 | } 135 | }; 136 | 137 | window.ScriptLoader = ScriptLoader; 138 | 139 | })(window); 140 | -------------------------------------------------------------------------------- /script-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Kuitos on 2014/05/29 13:29. 3 | * 脚本文件加载器 4 | * @author Kuitos lau 5 | * @tips 可配置 ScriptLoader.BASE_PATH 从而使用相对路径 6 | */ 7 | ; 8 | (function (window) { 9 | "use strict"; 10 | 11 | // dynamically add base tag as well as css and javascript files. 12 | // we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources 13 | // before the base attribute is added, causing 404 and terribly slow loading of the docs app. 14 | function outerHTML(node) { 15 | // if IE, Chrome take the internal method otherwise build one 16 | return node.outerHTML || (function (n) { 17 | var div = document.createElement('div'), h; 18 | div.appendChild(n); 19 | h = div.innerHTML; 20 | div = null; 21 | return h; 22 | })(node); 23 | } 24 | 25 | function creteScriptDom(scriptSrc) { 26 | var scriptDom = document.createElement("script"); 27 | // 如果以http://或https://开头,则使用原始路径 28 | scriptDom.src = ~scriptSrc.search(/^((http|https):\/\/)/g) ? scriptSrc : (ScriptLoader.BASE_PATH + scriptSrc); 29 | 30 | return scriptDom; 31 | } 32 | 33 | var headEl = document.getElementsByTagName('head')[0], 34 | loadedScripts = [], // 已加载过的脚本缓存 35 | slice = Array.prototype.slice; 36 | 37 | var ScriptLoader = { 38 | 39 | BASE_PATH: "", 40 | 41 | /** 42 | * 同步方式加载script,这种方式加载多个脚本浏览器会并行下载(仅在IE下)且按标签顺序执行,但是会阻塞后续其他资源(即后面script标签里js代码的执行) 43 | * @warning 这种方式如果是在页面加载之后调用会重写整个文档流,请谨慎使用!! 44 | * @param arguments 同步加载脚本列表 45 | */ 46 | loadSync: function () { 47 | 48 | var scriptList = slice.call(arguments); 49 | 50 | scriptList.forEach(function (scriptSrc) { 51 | 52 | // 当前脚本第一次加载 53 | if (!~loadedScripts.indexOf(scriptSrc)) { 54 | 55 | document.write(outerHTML(creteScriptDom(scriptSrc))); 56 | 57 | loadedScripts.push(scriptSrc); 58 | } 59 | 60 | }); 61 | 62 | return ScriptLoader; 63 | }, 64 | 65 | /** 66 | * 异步方式加载script,这种方式加载多个脚本浏览器会并行下载且一下载完成就执行(执行顺序依据下载完成时间,所以顺序不定),但是不会阻塞后续其他资源下载 67 | * innerHTML之类的方式只会往文档中插入节点,并不会触发浏览器解析script标签(下载并执行的能力) 68 | * @param scriptList 需要加载的文件列表 69 | * @param loadedCallback 脚本队列执行完之后执行的回调 70 | */ 71 | loadAsync: function () { 72 | 73 | var counter = 0, 74 | scriptList = slice.call(arguments), 75 | loadedCallback = scriptList[scriptList.length - 1] || function noop() { 76 | }, 77 | nonLoadScripts; 78 | 79 | if (typeof loadedCallback === 'function') { 80 | scriptList.pop(); 81 | } 82 | 83 | nonLoadScripts = scriptList.filter(function (scriptSrc) { 84 | return !~loadedScripts.indexOf(scriptSrc); 85 | }); // 未加载过的脚本列表 86 | 87 | if (nonLoadScripts.length) { 88 | 89 | nonLoadScripts.forEach(function (scriptSrc) { 90 | 91 | var scriptDom; 92 | 93 | // 脚本第一次加载 94 | if (!~loadedScripts.indexOf(scriptSrc)) { 95 | 96 | counter++; 97 | 98 | scriptDom = creteScriptDom(scriptSrc); 99 | addCallbackWhenScriptLoaded(scriptDom, loadedCallback); 100 | headEl.appendChild(scriptDom); 101 | 102 | loadedScripts.push(scriptSrc); 103 | } 104 | }); 105 | 106 | } else { 107 | loadedCallback(); 108 | } 109 | 110 | return ScriptLoader; 111 | 112 | function addCallbackWhenScriptLoaded(scriptDom, func) { 113 | 114 | scriptDom.onloadDone = false; 115 | 116 | // 脚本执行完成之后执行的回调,onreadystatechange在IE中有效,onload在其他浏览器中有效 117 | scriptDom.onload = function () { 118 | // 当所有loaded的脚本加载完成之后执行回调函数 119 | if (!(--counter)) { 120 | func(); 121 | } 122 | 123 | // helpful for gc 124 | scriptDom = null; 125 | }; 126 | scriptDom.onreadystatechange = function () { 127 | 128 | if (("loaded" === scriptDom.readyState || "complete" === scriptDom.readyState) && !scriptDom.onloadDone) { 129 | scriptDom.onloadDone = true; 130 | scriptDom.onload(); 131 | } 132 | }; 133 | 134 | } 135 | }, 136 | 137 | /** 138 | * 异步的方式加载脚本同时按顺序同步执行脚本 139 | */ 140 | loadAsyncExecInOrder: function () { 141 | 142 | var scriptList = slice.call(arguments); 143 | 144 | function loadScript(scriptList) { 145 | 146 | var scriptSrc = scriptList.shift(); 147 | 148 | if (typeof scriptList[0] === 'function') { 149 | ScriptLoader.loadAsync(scriptSrc, scriptList[0]); 150 | } else { 151 | ScriptLoader.loadAsync(scriptSrc, function () { 152 | loadScript(scriptList); 153 | }); 154 | } 155 | 156 | } 157 | 158 | loadScript(scriptList); 159 | 160 | return ScriptLoader; 161 | 162 | }, 163 | 164 | /** 165 | * 为最大提高网站加载速度,将不会立即用到的资源延时加载,避免跟需要立即执行的脚本抢占线程 166 | */ 167 | loadScriptAsyncDelayed: function () { 168 | 169 | var args = slice.call(arguments); 170 | 171 | window.setTimeout(function () { 172 | ScriptLoader.loadScriptsAsync.apply(ScriptLoader, args); 173 | }, 500); 174 | } 175 | }; 176 | 177 | window.ScriptLoader = ScriptLoader; 178 | 179 | })(window); 180 | --------------------------------------------------------------------------------