├── .gitignore ├── LICENSE.md ├── README.md ├── index.js ├── package.json ├── patch.js └── plugin.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arthur Stolyar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `async-module-loader` for webpack 2 | 3 | _Based on https://github.com/webpack/bundle-loader with improvements of error handling_ 4 | 5 | ``npm install async-module-loader`` 6 | 7 | ## Usage 8 | 9 | [webpack documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html) 10 | 11 | Also you will need to [use `AsyncModulePlugin`](#plugin). 12 | 13 | ### Basic usage 14 | 15 | `async-module-loader` returns function which accepts 2 callbacks: for success and for fail 16 | Exports of the requested module are passed into success callback as a first argument 17 | 18 | ```js 19 | require('async-module-loader!./file.js')(function onLoad(mod) { 20 | mod.doSomething(); 21 | }, function onError() { 22 | // error happened 23 | }); 24 | ``` 25 | 26 | Also you can use Promises with `promise` option specified, like this: 27 | 28 | ```js 29 | require('async-module-loader?promise!./file.js').then(function onLoad(mod) { 30 | mod.doSomething(); 31 | }, function onError() { 32 | // error happened 33 | }); 34 | ``` 35 | 36 | ### Specifying a chunk name 37 | 38 | ```js 39 | require('async-module-loader?name=my-chunk!./file.js')(function onLoad(mod) { 40 | mod.doSomething(); 41 | }, function onError() { 42 | // error happened 43 | }); 44 | ``` 45 | 46 | ### Delayed execution 47 | 48 | If you do not want your module to be executed immediately (maybe because some animation is in play), then you can tell to `async-module-loader` to load a chunk, but not execute it. In such, a function will be passed to the success callback instead of a `module.exports` object of requested chunk. Call that function then you will need you chunk executed: 49 | 50 | ```js 51 | require('async-module-loader?noexec!./file.js')(function onLoad(executeChunk) { 52 | setTimeout(function() { 53 | var mod = executeChunk(); 54 | mod.doSomething(); 55 | }, 500); 56 | }, function onError() { 57 | // error happened 58 | }); 59 | ``` 60 | 61 | ## Plugin 62 | 63 | To make `async-module-loader` work correctly you need to add `AsyncModulePlugin` to your plugins. 64 | 65 | ```js 66 | // webpack.config.js 67 | 68 | var AsyncModulePlugin = require('async-module-loader/plugin'); 69 | 70 | module.exports = { 71 | // ... 72 | 73 | plugins: [ 74 | // ... other plugins 75 | 76 | new AsyncModulePlugin() 77 | ] 78 | // ... 79 | } 80 | ``` 81 | 82 | ## Query parameters 83 | 84 | * `name`: Use this to specify output name for requested chunk. See [webpack documentation](https://github.com/webpack/loader-utils#interpolatename) 85 | 86 | * `promise`: Use this to return a promise from `async-module-loader`. 87 | 88 | * `noexec`: Use this to delay chunk execution 89 | 90 | ## License 91 | 92 | MIT (http://www.opensource.org/licenses/mit-license) 93 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Arthur Stolyar 4 | 5 | Based on Tobias Koppers @sokra bundle-loader 6 | https://github.com/webpack/bundle-loader 7 | */ 8 | var loaderUtils = require('loader-utils'); 9 | var path = require('path'); 10 | 11 | module.exports = function() {}; 12 | module.exports.pitch = function(remainingRequest) { 13 | this.cacheable && this.cacheable(); 14 | 15 | var query = loaderUtils.parseQuery(this.query); 16 | var chunkName = ''; 17 | 18 | if (query.name) { 19 | var options = { 20 | context: query.context || this.options.context, 21 | regExp: query.regExp 22 | }; 23 | 24 | chunkName = loaderUtils.interpolateName(this, query.name, options); 25 | chunkName = ', ' + JSON.stringify(chunkName); 26 | } 27 | 28 | var request = loaderUtils.stringifyRequest(this, '!!' + remainingRequest); 29 | var callback; 30 | 31 | if (query.noexec) { 32 | callback = 'callback(function() { return require(' + request + ') })'; 33 | } else { 34 | callback = 'callback(require(' + request + '))'; 35 | } 36 | 37 | var executor = [ 38 | 'function(callback, errback) {', 39 | ' require.ensure([], function(_, error) {', 40 | ' if (error) {', 41 | ' errback();', 42 | ' } else {', 43 | ' ' + callback, 44 | ' }', 45 | ' }' + chunkName + ');', 46 | '}' 47 | ].join('\n'); 48 | 49 | var result = [ 50 | 'require(' + loaderUtils.stringifyRequest(this, '!' + path.join(__dirname, 'patch.js')) + ');', 51 | 'module.exports = ' + (query.promise ? 'new Promise(' + executor + ')' : executor), 52 | ]; 53 | 54 | return result.join('\n'); 55 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-module-loader", 3 | "version": "2.1.0", 4 | "author": "Arthur Stolyar ", 5 | "description": "Async modules (chunks) loader for webpack with built-in error callbacks", 6 | "main": "index.js", 7 | "dependencies": { 8 | "loader-utils": "0.2.x" 9 | }, 10 | "keywords": [ 11 | "webpack", 12 | "async", 13 | "module", 14 | "chunk", 15 | "loader", 16 | "error", 17 | "jsonp" 18 | ], 19 | "licenses": "MIT", 20 | "repository": "NekR/async-module-loader" 21 | } 22 | -------------------------------------------------------------------------------- /patch.js: -------------------------------------------------------------------------------- 1 | patch(); 2 | 3 | function patch() { 4 | var head = document.querySelector('head'); 5 | var ensure = __webpack_require__.e; 6 | var chunks = __webpack_require__.s; 7 | var failures; 8 | 9 | __webpack_require__.e = function(chunkId, callback) { 10 | var loaded = false; 11 | var immediate = true; 12 | 13 | var handler = function(error) { 14 | if (!callback) return; 15 | 16 | callback(__webpack_require__, error); 17 | callback = null; 18 | }; 19 | 20 | if (!chunks && failures && failures[chunkId]) { 21 | handler(true); 22 | return; 23 | } 24 | 25 | ensure(chunkId, function() { 26 | if (loaded) return; 27 | loaded = true; 28 | 29 | if (immediate) { 30 | // webpack fires callback immediately if chunk was already loaded 31 | // IE also fires callback immediately if script was already 32 | // in a cache (AppCache counts too) 33 | setTimeout(function() { 34 | handler(); 35 | }); 36 | } else { 37 | handler(); 38 | } 39 | }); 40 | 41 | // This is |true| if chunk is already loaded and does not need onError call. 42 | // This happens because in such case ensure() is performed in sync way 43 | if (loaded) { 44 | return; 45 | } 46 | 47 | immediate = false; 48 | 49 | onError(function() { 50 | if (loaded) return; 51 | loaded = true; 52 | 53 | if (chunks) { 54 | chunks[chunkId] = void 0; 55 | } else { 56 | failures || (failures = {}); 57 | failures[chunkId] = true; 58 | } 59 | 60 | handler(true); 61 | }); 62 | }; 63 | 64 | function onError(callback) { 65 | var script = head.lastChild; 66 | 67 | if (script.tagName !== 'SCRIPT') { 68 | if (typeof console !== 'undefined' && console.warn) { 69 | console.warn('Script is not a script', script); 70 | } 71 | 72 | return; 73 | } 74 | 75 | script.onload = script.onerror = function() { 76 | script.onload = script.onerror = null; 77 | setTimeout(callback, 0); 78 | }; 79 | }; 80 | }; -------------------------------------------------------------------------------- /plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function() {}; 2 | module.exports.prototype.apply = function(compiler) { 3 | compiler.plugin('compilation', function(compilation) { 4 | compilation.mainTemplate.plugin('require-extensions', function(source, chunk, hash) { 5 | if (chunk.chunks.length > 0) { 6 | var buf = []; 7 | 8 | buf.push(''); 9 | buf.push(''); 10 | buf.push('// expose the chunks object'); 11 | buf.push(this.requireFn + '.s = installedChunks;'); 12 | 13 | return source + this.asString(buf); 14 | } 15 | 16 | return source; 17 | }); 18 | }); 19 | } --------------------------------------------------------------------------------