├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── client.js ├── dist ├── system-polyfill.js └── system-polyfill.min.js ├── index.js ├── package.json ├── server.js └── tests ├── benchmark ├── run.js └── src │ ├── evens.js │ ├── main.js │ └── odds.js └── unit ├── manual.html ├── src ├── circular-init │ ├── a.js │ ├── b.js │ └── c.js ├── fixtures │ ├── bar.js │ ├── baz.js │ └── foo.js ├── live │ ├── a.js │ └── b.js └── others │ ├── evens.js │ └── odds.js ├── system-live-test.js └── system.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tests/benchmark/cjs 3 | tests/benchmark/system 4 | tests/benchmark/bundle 5 | tests/unit/build 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | var libpath = require('path'); 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | jshint: { 8 | all: ['server.js', 'client.js', 'tests/*.js'] 9 | }, 10 | copy: { 11 | dist: { 12 | files: [ 13 | { 14 | 'dist/system-polyfill.js': ['client.js'] 15 | } 16 | ] 17 | } 18 | }, 19 | uglify: { 20 | dist: { 21 | files: [ 22 | { 23 | 'dist/system-polyfill.min.js': ['dist/system-polyfill.js'] 24 | } 25 | ] 26 | } 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-jshint'); 31 | grunt.loadNpmTasks('grunt-contrib-uglify'); 32 | grunt.loadNpmTasks('grunt-contrib-copy'); 33 | 34 | grunt.registerTask('build', ['copy', 'uglify']); 35 | grunt.registerTask('default', ['jshint', 'build']); 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Caridy Patiño 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | es6-micro-loader 2 | ================ 3 | 4 | ES6 System Loader Polyfill 5 | 6 | ## Overview 7 | 8 | This package implements a `System` polyfill that works on a browser, and in nodejs. The reason why we call this a micro loader is because it sides with the developers, implementing only a very tiny part of the specs to maintain the semantic of the ES modules. 9 | 10 | If you're looking for a full implementation of the ES6 Loader specs, you should check [SystemJS][] instead. Modules transpiled into `System.register()` using [es6-module-transpiler-system-formatter][] or [traceur-compiler][] should work fine with both implementations. 11 | 12 | The browser version of the loader is meant to be used to call `System.import('module-name')` to access ES6 modules included in the page after loading the transpiled modules using script tags. While, the nodejs version, is meant to be used by calling `System.import('path/to/module-name')`. 13 | 14 | ## Disclaimer 15 | 16 | This format is experimental, and it is a living creature, we will continue to tweak it until we fill it is good enough, and then we will change it again :p 17 | 18 | ## Usage 19 | 20 | ### In a browser 21 | 22 | The browser polyfill for `System` is available in this package in `dist/system-polyfill.js` and the corresponding minified version of it. This polyfill has to be included in the page before calling `System.import()` and before loading any transpiled module. 23 | 24 | _note: it provides basic network fetching mechanism to fetch modules using `script` tags by relying on `System.baseURL` configuration, which defaults to `/`, to construct the url to fetch the transpiled script in a non-blocking fashion. recommendation is to have all modules available locally somehow, right after including the polyfill in the page and not rely on the basic fetching routine that this polyfill provides. 25 | 26 | _note: you might also need to load a Promise polyfill. we recommend to use `es6-promise` or `bluebird`._ 27 | 28 | Once `System` is available in a page, you can load the transpiled modules, where no order is required. E.g.: 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | then you can simply use the imperative form to import any of the available modules, e.g: 38 | 39 | ```javascript 40 | System.has('named/foo'); // -> true 41 | System.has('named/wrong'); // -> false 42 | System.import('named/foo').then(function (foo) { 43 | foo.init(); // do something 44 | }).catch(function (err) { 45 | console.log(err); 46 | }); 47 | ``` 48 | 49 | ### In nodejs 50 | 51 | Install the npm package to access the `System` polyfill: 52 | 53 | ``` 54 | npm install es6-micro-loader --save 55 | ``` 56 | 57 | This package exports the `System` polyfill, so you can invoke `System.import()`, `System.get()` or `System.has()`: 58 | 59 | ```javascript 60 | var System = require('es6-micro-loader'); 61 | System.import("global/path/to/module"); 62 | ``` 63 | 64 | _note: the difference between a relative path, which is used when using `require()` in nodejs, and the global path to the module, which is what `System.import()` is expecting, is important. what is the "global/path/to/module" in the previous example? it is effectible the path from the root of the application (aka `process.env.PWD`) to the file in question. If you build a file into `build/foo.js`, then you use `build/foo` from any script in your application, independently of the `__dirname` or `__filename` from where you want to import `foo`._ 65 | 66 | 67 | # Benchmark 68 | 69 | For the server side, you could do the same by transpiling the modules into CommonJS using [es6-module-transpiler][] and using `require('./path/to/module-name')`, and even try to use them thru [browserify], although, `System.register()` form provides order of magnituds better performance than CommonJS output, due to the nature of the live bindings in ES6 Modules. You can run benchmark tests in this project: 70 | 71 | ```bash 72 | git clone https://github.com/caridy/es6-micro-loader.git 73 | cd es6-micro-loader 74 | npm install 75 | npm run benchmark 76 | ``` 77 | 78 | Benchmark results on the latest version: 79 | 80 | ```bash 81 | cjs x 2,847,803 ops/sec ±0.90% (92 runs sampled) 82 | system x 39,078,259 ops/sec ±0.78% (94 runs sampled) 83 | bundle x 50,916,706 ops/sec ±1.21% (82 runs sampled) 84 | Fastest is bundle 85 | ``` 86 | 87 | You can look into the benchmark code here: https://github.com/caridy/es6-micro-loader/tree/master/tests/benchmark 88 | 89 | [es6-module-transpiler-system-formatter]: https://github.com/caridy/es6-module-transpiler-system-formatter 90 | [SystemJS]: https://github.com/systemjs/systemjs 91 | [es6-module-transpiler]: https://github.com/square/es6-module-transpiler 92 | [traceur-compiler]: https://github.com/google/traceur-compiler 93 | [browserify]: http://browserify.org/ 94 | 95 | 96 | ## Contributing 97 | 98 | 1. Fork it 99 | 2. Create your feature branch (`git checkout -b my-new-feature`) 100 | 3. Commit your changes (`git commit -am 'Add some feature'`) 101 | 4. Push to the branch (`git push origin my-new-feature`) 102 | 5. Create new Pull Request 103 | 104 | Thanks, and enjoy living in the ES6 future! 105 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | (function(exports) { 2 | 3 | 'use strict'; 4 | 5 | var headEl = document.getElementsByTagName('head')[0], 6 | ie = /MSIE/.test(navigator.userAgent); 7 | 8 | /* 9 | normalizeName() is inspired by Ember's loader: 10 | https://github.com/emberjs/ember.js/blob/0591740685ee2c444f2cfdbcebad0bebd89d1303/packages/loader/lib/main.js#L39-L53 11 | */ 12 | function normalizeName(child, parentBase) { 13 | if (child.charAt(0) === '/') { 14 | child = child.slice(1); 15 | } 16 | if (child.charAt(0) !== '.') { 17 | return child; 18 | } 19 | var parts = child.split('/'); 20 | while (parts[0] === '.' || parts[0] === '..') { 21 | if (parts.shift() === '..') { 22 | parentBase.pop(); 23 | } 24 | } 25 | return parentBase.concat(parts).join('/'); 26 | } 27 | 28 | var seen = Object.create(null); 29 | var internalRegistry = Object.create(null); 30 | var externalRegistry = Object.create(null); 31 | var anonymousEntry; 32 | 33 | function ensuredExecute(name) { 34 | var mod = internalRegistry[name]; 35 | if (mod && !seen[name]) { 36 | seen[name] = true; 37 | // one time operation to execute the module body 38 | mod.execute(); 39 | } 40 | return mod && mod.proxy; 41 | } 42 | 43 | function set(name, values) { 44 | externalRegistry[name] = values; 45 | } 46 | 47 | function get(name) { 48 | return externalRegistry[name] || ensuredExecute(name); 49 | } 50 | 51 | function has(name) { 52 | return !!externalRegistry[name] || !!internalRegistry[name]; 53 | } 54 | 55 | function createScriptNode(src, callback) { 56 | var node = document.createElement('script'); 57 | // use async=false for ordered async? 58 | // parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order 59 | if (node.async) { 60 | node.async = false; 61 | } 62 | if (ie) { 63 | node.onreadystatechange = function() { 64 | if (/loaded|complete/.test(this.readyState)) { 65 | this.onreadystatechange = null; 66 | callback(); 67 | } 68 | }; 69 | } else { 70 | node.onload = node.onerror = callback; 71 | } 72 | node.setAttribute('src', src); 73 | headEl.appendChild(node); 74 | } 75 | 76 | function load(name) { 77 | return new Promise(function(resolve, reject) { 78 | createScriptNode((System.baseURL || '/') + name + '.js', function(err) { 79 | if (anonymousEntry) { 80 | System.register(name, anonymousEntry[0], anonymousEntry[1]); 81 | anonymousEntry = undefined; 82 | } 83 | var mod = internalRegistry[name]; 84 | if (!mod) { 85 | reject(new Error('Error loading module ' + name)); 86 | return; 87 | } 88 | Promise.all(mod.deps.map(function (dep) { 89 | if (externalRegistry[dep] || internalRegistry[dep]) { 90 | return Promise.resolve(); 91 | } 92 | return load(dep); 93 | })).then(resolve, reject); 94 | }); 95 | }); 96 | } 97 | 98 | 99 | var System = { 100 | set: set, 101 | get: get, 102 | has: has, 103 | import: function(name) { 104 | return new Promise(function(resolve, reject) { 105 | var normalizedName = normalizeName(name, []); 106 | var mod = get(normalizedName); 107 | return mod ? resolve(mod) : load(name).then(function () { 108 | return get(normalizedName); 109 | }); 110 | }); 111 | }, 112 | register: function(name, deps, wrapper) { 113 | if (Array.isArray(name)) { 114 | // anounymous module 115 | anonymousEntry = []; 116 | anonymousEntry.push.apply(anonymousEntry, arguments); 117 | return; // breaking to let the script tag to name it. 118 | } 119 | var proxy = Object.create(null), 120 | values = Object.create(null), 121 | mod, meta; 122 | // creating a new entry in the internal registry 123 | internalRegistry[name] = mod = { 124 | // live bindings 125 | proxy: proxy, 126 | // exported values 127 | values: values, 128 | // normalized deps 129 | deps: deps.map(function(dep) { 130 | return normalizeName(dep, name.split('/').slice(0, -1)); 131 | }), 132 | // other modules that depends on this so we can push updates into those modules 133 | dependants: [], 134 | // method used to push updates of deps into the module body 135 | update: function(moduleName, moduleObj) { 136 | meta.setters[mod.deps.indexOf(moduleName)](moduleObj); 137 | }, 138 | execute: function() { 139 | mod.deps.map(function(dep) { 140 | var imports = externalRegistry[dep]; 141 | if (imports) { 142 | mod.update(dep, imports); 143 | } else { 144 | imports = get(dep) && internalRegistry[dep].values; // optimization to pass plain values instead of bindings 145 | if (imports) { 146 | internalRegistry[dep].dependants.push(name); 147 | mod.update(dep, imports); 148 | } 149 | } 150 | }); 151 | meta.execute(); 152 | } 153 | }; 154 | // collecting execute() and setters[] 155 | meta = wrapper(function(identifier, value) { 156 | values[identifier] = value; 157 | mod.lock = true; // locking down the updates on the module to avoid infinite loop 158 | mod.dependants.forEach(function(moduleName) { 159 | if (internalRegistry[moduleName] && !internalRegistry[moduleName].lock) { 160 | internalRegistry[moduleName].update(name, values); 161 | } 162 | }); 163 | mod.lock = false; 164 | if (!Object.getOwnPropertyDescriptor(proxy, identifier)) { 165 | Object.defineProperty(proxy, identifier, { 166 | enumerable: true, 167 | get: function() { 168 | return values[identifier]; 169 | } 170 | }); 171 | } 172 | return value; 173 | }); 174 | } 175 | }; 176 | 177 | // exporting the System object 178 | exports.System = System; 179 | 180 | })(window); 181 | -------------------------------------------------------------------------------- /dist/system-polyfill.js: -------------------------------------------------------------------------------- 1 | (function(exports) { 2 | 3 | 'use strict'; 4 | 5 | var headEl = document.getElementsByTagName('head')[0], 6 | ie = /MSIE/.test(navigator.userAgent); 7 | 8 | /* 9 | normalizeName() is inspired by Ember's loader: 10 | https://github.com/emberjs/ember.js/blob/0591740685ee2c444f2cfdbcebad0bebd89d1303/packages/loader/lib/main.js#L39-L53 11 | */ 12 | function normalizeName(child, parentBase) { 13 | if (child.charAt(0) === '/') { 14 | child = child.slice(1); 15 | } 16 | if (child.charAt(0) !== '.') { 17 | return child; 18 | } 19 | var parts = child.split('/'); 20 | while (parts[0] === '.' || parts[0] === '..') { 21 | if (parts.shift() === '..') { 22 | parentBase.pop(); 23 | } 24 | } 25 | return parentBase.concat(parts).join('/'); 26 | } 27 | 28 | var seen = Object.create(null); 29 | var internalRegistry = Object.create(null); 30 | var externalRegistry = Object.create(null); 31 | var anonymousEntry; 32 | 33 | function ensuredExecute(name) { 34 | var mod = internalRegistry[name]; 35 | if (mod && !seen[name]) { 36 | seen[name] = true; 37 | // one time operation to execute the module body 38 | mod.execute(); 39 | } 40 | return mod && mod.proxy; 41 | } 42 | 43 | function set(name, values) { 44 | externalRegistry[name] = values; 45 | } 46 | 47 | function get(name) { 48 | return externalRegistry[name] || ensuredExecute(name); 49 | } 50 | 51 | function has(name) { 52 | return !!externalRegistry[name] || !!internalRegistry[name]; 53 | } 54 | 55 | function createScriptNode(src, callback) { 56 | var node = document.createElement('script'); 57 | // use async=false for ordered async? 58 | // parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order 59 | if (node.async) { 60 | node.async = false; 61 | } 62 | if (ie) { 63 | node.onreadystatechange = function() { 64 | if (/loaded|complete/.test(this.readyState)) { 65 | this.onreadystatechange = null; 66 | callback(); 67 | } 68 | }; 69 | } else { 70 | node.onload = node.onerror = callback; 71 | } 72 | node.setAttribute('src', src); 73 | headEl.appendChild(node); 74 | } 75 | 76 | function load(name) { 77 | return new Promise(function(resolve, reject) { 78 | createScriptNode((System.baseURL || '/') + name + '.js', function(err) { 79 | if (anonymousEntry) { 80 | System.register(name, anonymousEntry[0], anonymousEntry[1]); 81 | anonymousEntry = undefined; 82 | } 83 | var mod = internalRegistry[name]; 84 | if (!mod) { 85 | reject(new Error('Error loading module ' + name)); 86 | return; 87 | } 88 | Promise.all(mod.deps.map(function (dep) { 89 | if (externalRegistry[dep] || internalRegistry[dep]) { 90 | return Promise.resolve(); 91 | } 92 | return load(dep); 93 | })).then(resolve, reject); 94 | }); 95 | }); 96 | } 97 | 98 | 99 | var System = { 100 | set: set, 101 | get: get, 102 | has: has, 103 | import: function(name) { 104 | return new Promise(function(resolve, reject) { 105 | var normalizedName = normalizeName(name, []); 106 | var mod = get(normalizedName); 107 | return mod ? resolve(mod) : load(name).then(function () { 108 | return get(normalizedName); 109 | }); 110 | }); 111 | }, 112 | register: function(name, deps, wrapper) { 113 | if (Array.isArray(name)) { 114 | // anounymous module 115 | anonymousEntry = []; 116 | anonymousEntry.push.apply(anonymousEntry, arguments); 117 | return; // breaking to let the script tag to name it. 118 | } 119 | var proxy = Object.create(null), 120 | values = Object.create(null), 121 | mod, meta; 122 | // creating a new entry in the internal registry 123 | internalRegistry[name] = mod = { 124 | // live bindings 125 | proxy: proxy, 126 | // exported values 127 | values: values, 128 | // normalized deps 129 | deps: deps.map(function(dep) { 130 | return normalizeName(dep, name.split('/').slice(0, -1)); 131 | }), 132 | // other modules that depends on this so we can push updates into those modules 133 | dependants: [], 134 | // method used to push updates of deps into the module body 135 | update: function(moduleName, moduleObj) { 136 | meta.setters[mod.deps.indexOf(moduleName)](moduleObj); 137 | }, 138 | execute: function() { 139 | mod.deps.map(function(dep) { 140 | var imports = externalRegistry[dep]; 141 | if (imports) { 142 | mod.update(dep, imports); 143 | } else { 144 | imports = get(dep) && internalRegistry[dep].values; // optimization to pass plain values instead of bindings 145 | if (imports) { 146 | internalRegistry[dep].dependants.push(name); 147 | mod.update(dep, imports); 148 | } 149 | } 150 | }); 151 | meta.execute(); 152 | } 153 | }; 154 | // collecting execute() and setters[] 155 | meta = wrapper(function(identifier, value) { 156 | values[identifier] = value; 157 | mod.lock = true; // locking down the updates on the module to avoid infinite loop 158 | mod.dependants.forEach(function(moduleName) { 159 | if (internalRegistry[moduleName] && !internalRegistry[moduleName].lock) { 160 | internalRegistry[moduleName].update(name, values); 161 | } 162 | }); 163 | mod.lock = false; 164 | if (!Object.getOwnPropertyDescriptor(proxy, identifier)) { 165 | Object.defineProperty(proxy, identifier, { 166 | enumerable: true, 167 | get: function() { 168 | return values[identifier]; 169 | } 170 | }); 171 | } 172 | return value; 173 | }); 174 | } 175 | }; 176 | 177 | // exporting the System object 178 | exports.System = System; 179 | 180 | })(window); 181 | -------------------------------------------------------------------------------- /dist/system-polyfill.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";function b(a,b){if("/"===a.charAt(0)&&(a=a.slice(1)),"."!==a.charAt(0))return a;for(var c=a.split("/");"."===c[0]||".."===c[0];)".."===c.shift()&&b.pop();return b.concat(c).join("/")}function c(a){var b=m[a];return b&&!l[a]&&(l[a]=!0,b.execute()),b&&b.proxy}function d(a,b){n[a]=b}function e(a){return n[a]||c(a)}function f(a){return!!n[a]||!!m[a]}function g(a,b){var c=document.createElement("script");c.async&&(c.async=!1),k?c.onreadystatechange=function(){/loaded|complete/.test(this.readyState)&&(this.onreadystatechange=null,b())}:c.onload=c.onerror=b,c.setAttribute("src",a),j.appendChild(c)}function h(a){return new Promise(function(b,c){g((o.baseURL||"/")+a+".js",function(){i&&(o.register(a,i[0],i[1]),i=void 0);var d=m[a];return d?void Promise.all(d.deps.map(function(a){return n[a]||m[a]?Promise.resolve():h(a)})).then(b,c):void c(new Error("Error loading module "+a))})})}var i,j=document.getElementsByTagName("head")[0],k=/MSIE/.test(navigator.userAgent),l=Object.create(null),m=Object.create(null),n=Object.create(null),o={set:d,get:e,has:f,"import":function(a){return new Promise(function(c){var d=b(a,[]),f=e(d);return f?c(f):h(a).then(function(){return e(d)})})},register:function(a,c,d){if(Array.isArray(a))return i=[],void i.push.apply(i,arguments);var f,g,h=Object.create(null),j=Object.create(null);m[a]=f={proxy:h,values:j,deps:c.map(function(c){return b(c,a.split("/").slice(0,-1))}),dependants:[],update:function(a,b){g.setters[f.deps.indexOf(a)](b)},execute:function(){f.deps.map(function(b){var c=n[b];c?f.update(b,c):(c=e(b)&&m[b].values,c&&(m[b].dependants.push(a),f.update(b,c)))}),g.execute()}},g=d(function(b,c){return j[b]=c,f.lock=!0,f.dependants.forEach(function(b){m[b]&&!m[b].lock&&m[b].update(a,j)}),f.lock=!1,Object.getOwnPropertyDescriptor(h,b)||Object.defineProperty(h,b,{enumerable:!0,get:function(){return j[b]}}),c})}};a.System=o}(window); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jslint node:true */ 2 | 3 | if (global.System) { 4 | throw new Error("Conflicts with the global `System` definition, use `es6-micro-loader/lib/system` instead."); 5 | } 6 | 7 | global.Promise = global.Promise || require("es6-promise").Promise; 8 | module.exports = global.System = require("./server").System; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es6-micro-loader", 3 | "version": "0.2.1", 4 | "description": "Simplistic ES6 Module Loader for modules transpiled into System.register() format", 5 | "author": "Caridy Patino ", 6 | "homepage": "https://github.com/caridy/es6-micro-loader", 7 | "keywords": [ 8 | "es6", 9 | "module", 10 | "loader", 11 | "system", 12 | "register" 13 | ], 14 | "bugs": "https://github.com/caridy/es6-micro-loader/issues", 15 | "main": "index.js", 16 | "files": [ 17 | "index.js", 18 | "server.js", 19 | "dist/" 20 | ], 21 | "scripts": { 22 | "test": "npm run test-compile; ./node_modules/.bin/_mocha tests/unit/*.js --reporter spec", 23 | "test-compile": "cd tests/unit/src/; ../../../node_modules/.bin/compile-modules convert -f es6-module-transpiler-system-formatter **/*.js -o ../build", 24 | "benchmark-compile-cjs": "cd tests/benchmark/src/; ../../../node_modules/.bin/compile-modules convert -f commonjs *.js -o ../cjs", 25 | "benchmark-compile-bundle": "cd tests/benchmark/src/; ../../../node_modules/.bin/compile-modules convert -f bundle *.js -o ../bundle/main.js", 26 | "benchmark-compile-system": "cd tests/benchmark/src/; ../../../node_modules/.bin/compile-modules convert -f es6-module-transpiler-system-formatter *.js -o ../system", 27 | "benchmark": "npm run benchmark-compile-bundle; npm run benchmark-compile-cjs; npm run benchmark-compile-system; node tests/benchmark/run.js" 28 | }, 29 | "license": "MIT", 30 | "dependencies": { 31 | "es6-promise": "^1.0.0" 32 | }, 33 | "devDependencies": { 34 | "grunt": "^0.4.5", 35 | "grunt-cli": "0.1.*", 36 | "grunt-contrib-copy": "^0.5.0", 37 | "grunt-contrib-uglify": "^0.5.1", 38 | "grunt-contrib-jshint": "^0.10.0", 39 | "benchmark": "^1.0.0", 40 | "es6-module-transpiler": "~0.8.2", 41 | "es6-module-transpiler-system-formatter": "~0.2.0", 42 | "chai": "*", 43 | "mocha": "*", 44 | "xunit-file": "*", 45 | "chai-as-promised": "*" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | 3 | var path = require('path'); 4 | 5 | var seen = Object.create(null); 6 | var internalRegistry = Object.create(null); 7 | var externalRegistry = Object.create(null); 8 | 9 | function ensuredExecute (name) { 10 | var mod = internalRegistry[name]; 11 | if (mod && !seen[name]) { 12 | seen[name] = true; 13 | // one time operation to execute the module body 14 | mod.execute(); 15 | } 16 | return mod && mod.proxy; 17 | } 18 | function set (name, values) { 19 | externalRegistry[name] = values; 20 | } 21 | function get (name) { 22 | return externalRegistry[name] || ensuredExecute(name); 23 | } 24 | function has (name) { 25 | return !!externalRegistry[name] || !!internalRegistry[name]; 26 | } 27 | 28 | 29 | 30 | // exporting the System object 31 | exports.System = { 32 | set: set, 33 | get: get, 34 | has: has, 35 | import: function(name) { 36 | return new Promise(function (resolve, reject) { 37 | var mod = patchedRequire(path.resolve(name)); 38 | return mod ? resolve(mod) : reject(new Error('Could not find module ' + name)); 39 | }); 40 | }, 41 | register: function(name, deps, wrapper) { 42 | var mod, 43 | externalDeps = []; 44 | // creating a new module entry that will be added to the cache later on 45 | mod = { 46 | name: name, 47 | setters: null, 48 | proxy: Object.create(null), 49 | deps: deps.map(function(dep) { 50 | if (dep.charAt(0) !== '.') { 51 | externalDeps.push(dep); 52 | return dep; 53 | } 54 | var parts = dep.split('/'), 55 | parentBase = name.split('/').slice(0, -1); 56 | while (parts[0] === '.' || parts[0] === '..') { 57 | if (parts.shift() === '..') { 58 | parentBase.pop(); 59 | } 60 | } 61 | return parentBase.concat(parts).join('/'); 62 | }), 63 | externalDeps: externalDeps, 64 | // other modules that depends on this so we can push updates into those modules 65 | dependants: [], 66 | // method used to push updates of dependencies into the module body 67 | update: function(moduleName, moduleObj) { 68 | mod.setters[mod.deps.indexOf(moduleName)](moduleObj); 69 | }, 70 | execute: wrapper 71 | }; 72 | newEntry = mod; 73 | } 74 | }; 75 | 76 | 77 | 78 | var newEntry; 79 | var m = require('module'); 80 | var Module = require('module').Module; 81 | var originalLoader = require('module')._load; 82 | 83 | // monkey patching `require()` during a brief period of time 84 | function patchedRequire(name) { 85 | m._load = function patchedLoader(request, parent, isMain) { 86 | var values, filename, cachedModule, metaModule, esModule; 87 | newEntry = undefined; 88 | values = originalLoader.apply(this, arguments); 89 | if (newEntry) { 90 | filename = Module._resolveFilename(request, parent); 91 | cachedModule = Module._cache[filename]; 92 | if (cachedModule && !cachedModule._esModule) { 93 | cachedModule._esModule = esModule = newEntry; 94 | esModule.address = filename; 95 | esModule.basePath = request.slice(0, -esModule.name.length); 96 | esModule.parent = parent; 97 | // collecting execute() and setters[] 98 | metaModule = esModule.execute(function(identifier, value) { 99 | values[identifier] = value; 100 | esModule.lock = true; // locking down the updates on the module to avoid infinite loop 101 | esModule.dependants.forEach(function(dependant) { 102 | if (!dependant.lock) { 103 | dependant.update(esModule.name, values); 104 | } 105 | }); 106 | esModule.lock = false; 107 | if (!Object.getOwnPropertyDescriptor(esModule.proxy, identifier)) { 108 | Object.defineProperty(esModule.proxy, identifier, { 109 | enumerable: true, 110 | get: function() { 111 | return values[identifier]; 112 | } 113 | }); 114 | } 115 | return value; 116 | }); 117 | esModule.setters = metaModule.setters; 118 | esModule.deps.forEach(function(dep) { 119 | var imports = externalRegistry[dep], 120 | depRequest, depFilename, depModule; 121 | if (!imports) { 122 | if (~esModule.externalDeps.indexOf(dep)) { 123 | imports = require(Module._resolveFilename(dep, cachedModule)); 124 | } else { 125 | depRequest = path.resolve(path.join(esModule.basePath, dep)); 126 | imports = require(depRequest); 127 | depFilename = Module._resolveFilename(depRequest, cachedModule); 128 | depModule = Module._cache[depFilename]._esModule; 129 | if (depModule) { 130 | depModule.dependants.push(esModule); 131 | } 132 | } 133 | } 134 | esModule.update(dep, imports); 135 | }); 136 | // executing the module body 137 | metaModule.execute(); 138 | } 139 | } 140 | return values; 141 | }; 142 | var mod = require(name); 143 | // removing the patch 144 | m._load = originalLoader; 145 | return mod; 146 | } 147 | -------------------------------------------------------------------------------- /tests/benchmark/run.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require("benchmark"); 2 | var System = require("../../"); // es6-micro-loader 3 | 4 | System.import("tests/benchmark/system/main").then(function (systemMod) { 5 | 6 | var suite = new Benchmark.Suite(), 7 | a = 0, aFn = require("./cjs/main").checkForOdd, 8 | b = 0, bFn = systemMod.checkForOdd, 9 | c = 0, cFn = require("./bundle/main") && global.checkForOdd; 10 | 11 | // add tests 12 | suite.add("cjs", function() { 13 | // commonjs modules which use require() and defineProperties() to preserve ES semantics 14 | aFn(a++); 15 | }) 16 | .add("system", function() { 17 | // System.register() modules which preserve ES semantics 18 | bFn(b++); 19 | }) 20 | .add("bundle", function() { 21 | // using a global variable, and a rollup without preserving bindings or any other ES Semantics 22 | cFn(c++); 23 | }) 24 | // add listeners 25 | .on("cycle", function(event) { 26 | console.log(String(event.target)); 27 | }) 28 | .on("complete", function() { 29 | console.log("Fastest is " + this.filter("fastest").pluck("name")); 30 | }) 31 | // run async 32 | .run({ "async": true }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /tests/benchmark/src/evens.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | export default function (n) { 4 | return n % 2 === 0; 5 | } 6 | -------------------------------------------------------------------------------- /tests/benchmark/src/main.js: -------------------------------------------------------------------------------- 1 | /* jslint esnext: true */ 2 | 3 | import isOdd from "./odds"; 4 | 5 | export function checkForOdd(n) { 6 | return isOdd(n); 7 | } 8 | 9 | // this is needed for `bundle` output 10 | if (typeof global !== 'undefined') { 11 | global.checkForOdd = checkForOdd; 12 | } 13 | -------------------------------------------------------------------------------- /tests/benchmark/src/odds.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | import isEven from "./evens"; 4 | 5 | export default function (n) { 6 | return !isEven(n); 7 | } 8 | -------------------------------------------------------------------------------- /tests/unit/manual.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | using System polyfill thru micro-loader 6 | 7 | 8 | 9 |
Look at the browser's console for details.
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/unit/src/circular-init/a.js: -------------------------------------------------------------------------------- 1 | import B from './b'; 2 | import C from './c'; 3 | 4 | var b = new B(); 5 | var c = new C(); 6 | -------------------------------------------------------------------------------- /tests/unit/src/circular-init/b.js: -------------------------------------------------------------------------------- 1 | import C from './c'; 2 | 3 | function B() { 4 | console.log('B'); 5 | } 6 | 7 | B.prototype.createC = function() { 8 | return new C(); 9 | }; 10 | 11 | export default B; 12 | -------------------------------------------------------------------------------- /tests/unit/src/circular-init/c.js: -------------------------------------------------------------------------------- 1 | import B from './b'; 2 | 3 | function C() { 4 | B.call(this); 5 | console.log('C'); 6 | } 7 | 8 | C.prototype = Object.create(B.prototype); 9 | C.prototype.constructor = C; 10 | 11 | export default C; 12 | -------------------------------------------------------------------------------- /tests/unit/src/fixtures/bar.js: -------------------------------------------------------------------------------- 1 | import foo from "./foo"; 2 | 3 | export default function (a, b) { 4 | return foo(a, b); 5 | }; 6 | 7 | export function multi () { 8 | return foo.apply(this, arguments); 9 | } 10 | -------------------------------------------------------------------------------- /tests/unit/src/fixtures/baz.js: -------------------------------------------------------------------------------- 1 | import simple from "./bar"; 2 | import {multi} from "./bar"; 3 | 4 | class Baz { 5 | 6 | constructor(config) { 7 | this.config = config; 8 | } 9 | 10 | simple(a, b) { 11 | let r = simple(a, b); 12 | return r; 13 | } 14 | 15 | multi() { 16 | return multi.apply(this, arguments); 17 | } 18 | 19 | }; 20 | 21 | export default Baz; 22 | -------------------------------------------------------------------------------- /tests/unit/src/fixtures/foo.js: -------------------------------------------------------------------------------- 1 | 2 | var foo = { something: 1}; 3 | 4 | export default function () { 5 | var n = 0; 6 | Array.prototype.slice(arguments).forEach(function (v) { 7 | n += v; 8 | }); 9 | return n; 10 | }; 11 | -------------------------------------------------------------------------------- /tests/unit/src/live/a.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | import { counter, increment, decrement } from "./b"; 4 | 5 | export function up () { 6 | increment(); 7 | return counter; 8 | } 9 | 10 | export function down () { 11 | decrement(); 12 | return counter; 13 | } 14 | 15 | export function current () { 16 | return counter; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/src/live/b.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | export var counter = 0; 4 | 5 | export function increment () { 6 | counter++; 7 | } 8 | 9 | export function decrement () { 10 | counter--; 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/src/others/evens.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | import { nextOdd } from './odds'; 4 | 5 | /** 6 | * We go through these gymnastics to eager-bind to nextOdd. This is done to 7 | * ensure that both this module and the 'odds' module eagerly use something 8 | * from the other. 9 | */ 10 | export var nextEven = (function() { 11 | return function(n) { 12 | var no = nextOdd(n); 13 | return (no === n + 2) ? 14 | no - 1 : no; 15 | }; 16 | })(nextOdd); 17 | 18 | export function isEven(n) { 19 | return n % 2 === 0; 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/src/others/odds.js: -------------------------------------------------------------------------------- 1 | /* jshint esnext:true */ 2 | 3 | import { isEven } from './evens'; 4 | 5 | export function nextOdd(n) { 6 | return isEven(n) ? n + 1 : n + 2; 7 | } 8 | 9 | /** 10 | * We go through these gymnastics to eager-bind to isEven. This is done to 11 | * ensure that both this module and the 'evens' module eagerly use something 12 | * from the other. 13 | */ 14 | export var isOdd = (function(isEven) { 15 | return function(n) { 16 | return !isEven(n); 17 | }; 18 | })(isEven); 19 | -------------------------------------------------------------------------------- /tests/unit/system-live-test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 'use strict'; 3 | 4 | var System = require('../../'), 5 | expect = require('chai').expect; 6 | 7 | describe('System', function() { 8 | 9 | it('.import() should be a function', function() { 10 | expect(System.import).to.be.a('function'); 11 | }); 12 | 13 | describe('import("tests/unit/build/live/a")', function() { 14 | 15 | it('should support a live counter', function(next) { 16 | var p = System.import("tests/unit/build/live/a"); 17 | p.then(function (a) { 18 | expect(a.up).to.be.a('function'); 19 | expect(a.down).to.be.a('function'); 20 | expect(a.current).to.be.a('function'); 21 | expect(a.current()).to.be.a('number').equal(0); 22 | // testing live bindings 23 | a.up(); 24 | a.up(); 25 | expect(a.current()).to.be.a('number').equal(2); 26 | a.down(); 27 | expect(a.current()).to.be.a('number').equal(1); 28 | next(); 29 | }).catch(next); 30 | }); 31 | 32 | it('should maintain the state after second import() call', function(next) { 33 | var p = System.import("tests/unit/build/live/a"); 34 | p.then(function (a) { 35 | expect(a.up).to.be.a('function'); 36 | expect(a.down).to.be.a('function'); 37 | expect(a.current).to.be.a('function'); 38 | expect(a.current()).to.be.a('number').equal(1); 39 | next(); 40 | }).catch(next); 41 | }); 42 | 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /tests/unit/system.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach */ 2 | 'use strict'; 3 | 4 | var System = require('../../'), 5 | expect = require('chai').expect; 6 | 7 | describe('System', function() { 8 | 9 | it('.import() should be a function', function() { 10 | expect(System.import).to.be.a('function'); 11 | }); 12 | 13 | it('.has() should be a function', function() { 14 | expect(System.has).to.be.a('function'); 15 | }); 16 | 17 | it('.get() should be a function', function() { 18 | expect(System.get).to.be.a('function'); 19 | }); 20 | 21 | it('.register() should be a function', function() { 22 | expect(System.register).to.be.a('function'); 23 | }); 24 | 25 | }); 26 | --------------------------------------------------------------------------------