├── .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 |
--------------------------------------------------------------------------------