├── .gitignore ├── .zuul.yml ├── Makefile ├── Readme.md ├── component.json ├── index.d.ts ├── index.js ├── package.json └── test ├── hello.js ├── index.js └── throw.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-qunit 2 | browsers: 3 | - name: chrome 4 | version: [oldest, latest] 5 | - name: firefox 6 | version: [oldest, latest] 7 | - name: opera 8 | version: latest 9 | - name: safari 10 | version: oldest..latest 11 | - name: ie 12 | version: oldest..latest 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: index.js 2 | @component build --dev 3 | 4 | clean: 5 | rm -fr build 6 | 7 | .PHONY: clean 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # load-script 2 | 3 | Dynamic script loading. 4 | 5 | ## Installation 6 | 7 | via component 8 | 9 | ``` 10 | $ component install eldargab/load-script 11 | ``` 12 | 13 | via npm 14 | 15 | ``` 16 | $ npm install load-script 17 | ``` 18 | 19 | ## API 20 | `load-script` appends a `script` node to the `` element in the dom. 21 | 22 | `require('load-script')` returns a function of the following interface: `function(url[, opts][, cb]) {}` 23 | 24 | ### url 25 | Any url that you would like to load. May be absolute or relative. 26 | 27 | ### [, opts] 28 | A map of options. Here are the currently supported options: 29 | 30 | * `async` - A boolean value used for `script.async`. By default this is `true`. 31 | * `attrs` - A map of attributes to set on the `script` node before appending it to the DOM. By default this is empty. 32 | * `charset` - A string value used for `script.charset`. By default this is `utf8`. 33 | * `text` - A string of text to append to the `script` node before it is appended to the DOM. By default this is empty. 34 | * `type` - A string used for `script.type`. By default this is `text/javascript`. 35 | 36 | ### [, cb] 37 | A callback function of the following interface: `function(err, script) {}` where `err` is an error if any occurred and `script` is the `script` node that was appended to the DOM. 38 | 39 | ## Example Usage 40 | 41 | ```javascript 42 | var load = require('load-script') 43 | 44 | load('foo.js', function (err, script) { 45 | if (err) { 46 | // print useful message 47 | } 48 | else { 49 | console.log(script.src);// Prints 'foo'.js' 50 | // use script 51 | // note that in IE8 and below loading error wouldn't be reported 52 | } 53 | }) 54 | ``` 55 | 56 | ## License 57 | 58 | MIT 59 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "load-script", 3 | "repo": "eldargab/load-script", 4 | "description": "Dynamic script loading", 5 | "version": "0.0.5", 6 | "keywords": ["script", "load"], 7 | "license": "MIT", 8 | "scripts": [ 9 | "index.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | type Callback = (errorOrNull: Error | null, script: HTMLScriptElement) => void; 2 | 3 | type AllowedAttributes = 'type' | 'charset' | 'async' | 'text'; 4 | 5 | type Options = Partial> & { 6 | attrs?: Record; 7 | } 8 | 9 | declare function load (src: HTMLScriptElement['src'], opts: Callback): void; 10 | declare function load (src: HTMLScriptElement['src'], opts: Options, cb: Callback): void; 11 | 12 | export default load; 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function load (src, opts, cb) { 3 | var head = document.head || document.getElementsByTagName('head')[0] 4 | var script = document.createElement('script') 5 | 6 | if (typeof opts === 'function') { 7 | cb = opts 8 | opts = {} 9 | } 10 | 11 | opts = opts || {} 12 | cb = cb || function() {} 13 | 14 | script.type = opts.type || 'text/javascript' 15 | script.charset = opts.charset || 'utf8'; 16 | script.async = 'async' in opts ? !!opts.async : true 17 | script.src = src 18 | 19 | if (opts.attrs) { 20 | setAttributes(script, opts.attrs) 21 | } 22 | 23 | if (opts.text) { 24 | script.text = '' + opts.text 25 | } 26 | 27 | var onend = 'onload' in script ? stdOnEnd : ieOnEnd 28 | onend(script, cb) 29 | 30 | // some good legacy browsers (firefox) fail the 'in' detection above 31 | // so as a fallback we always set onload 32 | // old IE will ignore this and new IE will set onload 33 | if (!script.onload) { 34 | stdOnEnd(script, cb); 35 | } 36 | 37 | head.appendChild(script) 38 | } 39 | 40 | function setAttributes(script, attrs) { 41 | for (var attr in attrs) { 42 | script.setAttribute(attr, attrs[attr]); 43 | } 44 | } 45 | 46 | function stdOnEnd (script, cb) { 47 | script.onload = function () { 48 | this.onerror = this.onload = null 49 | cb(null, script) 50 | } 51 | script.onerror = function () { 52 | // this.onload = null here is necessary 53 | // because even IE9 works not like others 54 | this.onerror = this.onload = null 55 | cb(new Error('Failed to load ' + this.src), script) 56 | } 57 | } 58 | 59 | function ieOnEnd (script, cb) { 60 | script.onreadystatechange = function () { 61 | if (this.readyState != 'complete' && this.readyState != 'loaded') return 62 | this.onreadystatechange = null 63 | cb(null, script) // there is no way to catch loading errors in IE8 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "load-script", 3 | "description": "Dynamic script loading for browser", 4 | "version": "2.0.0", 5 | "types": "./index.d.ts", 6 | "keywords": [ 7 | "browser", 8 | "script", 9 | "load" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/eldargab/load-script" 14 | }, 15 | "scripts": { 16 | "test": "zuul -- test/index.js", 17 | "test-local": "zuul --local 9005 -- test/index.js" 18 | }, 19 | "devDependencies": { 20 | "zuul": "~2.1.0" 21 | }, 22 | "license": "MIT" 23 | } 24 | -------------------------------------------------------------------------------- /test/hello.js: -------------------------------------------------------------------------------- 1 | log('Hello world') 2 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var load = require('../') 3 | 4 | var last_msg = undefined; 5 | log = function(msg) { 6 | last_msg = msg; 7 | } 8 | 9 | test('success', function(done) { 10 | load('test/hello.js', function (err) { 11 | assert.ifError(err); 12 | assert.equal(last_msg, 'Hello world'); 13 | last_msg = undefined; 14 | done(); 15 | }) 16 | }); 17 | 18 | test('opts.async', function(done) { 19 | load('test/hello.js', {async: false}, function(err, script) { 20 | assert.ifError(err); 21 | assert.equal(script.async, false); 22 | done(); 23 | }) 24 | }); 25 | 26 | test('opts.attrs', function(done) { 27 | load('test/hello.js', {attrs: {foo: 'boo'}}, function(err, script) { 28 | assert.ifError(err); 29 | assert.equal(script.getAttribute('foo'), 'boo'); 30 | done(); 31 | }) 32 | }); 33 | 34 | test('opts.charset', function(done) { 35 | load('test/hello.js', {charset: 'iso-8859-1'}, function(err, script) { 36 | assert.ifError(err); 37 | assert.equal(script.charset, 'iso-8859-1'); 38 | done(); 39 | }) 40 | }); 41 | 42 | test('opts.text', function(done) { 43 | load('test/hello.js', {text: 'foo=5;'}, function(err, script) { 44 | assert.ifError(err); 45 | done(); 46 | }) 47 | }); 48 | 49 | test('opts.type', function(done) { 50 | load('test/hello.js', {type: 'text/ecmascript'}, function(err, script) { 51 | assert.ifError(err); 52 | assert.equal(script.type, 'text/ecmascript'); 53 | done(); 54 | }) 55 | }); 56 | 57 | test('no exist', function(done) { 58 | load('unexistent.js', function (err, legacy) { 59 | if (!legacy) { 60 | assert.ok(err); 61 | } 62 | 63 | var tid = setTimeout(function() { 64 | done(); 65 | }, 200); 66 | 67 | // some browsers will also throw as well as report erro 68 | var old = window.onerror; 69 | window.onerror = function(msg, file, line) { 70 | if (msg !== 'Error loading script') { 71 | assert(false); 72 | } 73 | window.onerror = old; 74 | clearTimeout(tid); 75 | done(); 76 | }; 77 | }) 78 | }); 79 | 80 | test('throw', function(done) { 81 | var old = window.onerror; 82 | // silence the script error 83 | window.onerror = function() {}; 84 | load('test/throw.js', function (err) { 85 | assert.ifError(err); 86 | window.onerror = old; 87 | done(); 88 | }) 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /test/throw.js: -------------------------------------------------------------------------------- 1 | throw new Error('Hello error') 2 | --------------------------------------------------------------------------------