├── .gitignore ├── .groc.json ├── .jshintrc ├── README.md ├── demo ├── custom-elements-old │ ├── index.html │ ├── squid.js │ └── squidbits.html ├── custom-elements │ ├── index.html │ ├── squid-es5-combined.html │ ├── squid-es5-split.js │ ├── squid-es6-combined.html │ ├── squid-es6-split.js │ └── squidbits.html ├── index.html └── scoped-script │ ├── awesomesauce.js │ ├── index.html │ └── swoot.html ├── dist ├── html-exports.debug.js ├── html-exports.js ├── html-exports.min.js └── sysjs-plugin │ ├── html.debug.js │ ├── html.js │ └── html.min.js ├── gulpfile.js ├── package.json ├── src ├── documentloader.js ├── loaderhooks.js ├── scopedscript.js ├── sysjs-plugin.js ├── system.js └── util.js └── test ├── fixtures ├── foo.js ├── nameddefault.html ├── resources.html ├── resources │ ├── import.html │ ├── script.js │ ├── style-import-nested.css │ ├── style-import.css │ └── style-link.css ├── scoped.html ├── scopedexport.html ├── simpledefault.html └── words.html ├── index.html ├── shared └── fixtures.js └── unit ├── exports.html ├── resources.html ├── scopedscript.html ├── systemjs.html └── systemjs └── thing.html /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /node_modules/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.groc.json: -------------------------------------------------------------------------------- 1 | { 2 | "glob": ["README.md", "src/**/*.js"], 3 | "github": false, 4 | "repository-url": "https://github.com/nevir/html-exports" 5 | } 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "eqeqeq": true, 4 | "forin": true, 5 | "indent": 2, 6 | "newcap": true, 7 | "noarg": true, 8 | "nonbsp": true, 9 | "quotmark": "single", 10 | "undef": true, 11 | "unused": true, 12 | "asi": true, 13 | "boss": true, 14 | "esnext": true, 15 | "browser": true, 16 | "node": true 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML Exports 2 | 3 | Why let JavaScript have all the fun? HTML documents can be modules too! 4 | 5 | 6 | ## The Syntax 7 | 8 | ### Declaring Exports 9 | 10 | Just like ES6 modules, you can choose particular values to export from a HTML document. Simply place an `export` attribute on any element with that you wish to export: 11 | 12 | ```html 13 |
...
14 | ``` 15 | 16 | You can also declare the default export of the document by tagging an element with `default` (`id` is optional). 17 | 18 | ```html 19 |
...
20 | ``` 21 | 22 | If you don't declare a default export, the document is exported. 23 | 24 | 25 | ### Declaring Dependencies 26 | 27 | You can also declare dependencies on other modules from HTML. 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | Similar to ES6's `import` syntax, you can import particular values from the module, too. A default export can be imported via: 34 | 35 | ```html 36 | 37 | ``` 38 | 39 | Named exports can also be imported via the `values` attribute: 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | _Not supported yet: Some way of aliasing named imports. The technical side is straightforward, but a decent syntax is unclear. Suggestions welcome!_ 46 | 47 | 48 | ### Using Imported Values 49 | 50 | We also introduce the concept of "scoped" ` 66 | ``` 67 | 68 | That example is equivalent to the ES6 form: 69 | 70 | ```html 71 | 72 | import $ from 'jQuery' 73 | $(() => { 74 | // doing things! 75 | }) 76 | 77 | ``` 78 | 79 | #### Exporting Values From Script 80 | 81 | As mentioned above, you can export values from a scoped script! These values are exported as part of the HTML module. You can even mix and match: 82 | 83 | ```html 84 | 85 | 90 | ``` 91 | 92 | 93 | ### Importing HTML Modules Via ES6 94 | 95 | Just treat a HTML module as you would any other module: 96 | 97 | ``` 98 | import { user, post } from './templates.html' 99 | ``` 100 | 101 | `user` and `post` are the _elements_ exported by `templates.html`! 102 | 103 | 104 | ## Using It 105 | 106 | Include [`html-exports.min.js`](dist/) (~0.8KB gzipped) in your page, after loading [es6-module-loader](https://github.com/ModuleLoader/es6-module-loader) and [SystemJS](https://github.com/systemjs/systemjs). 107 | 108 | ```html 109 | 110 | 111 | 112 | ``` 113 | 114 | This gives you the default behavior where any module with a `.html` extension is treated as a HTML module. SystemJS is only necessary if you are loading ES5 code (e.g. CommonJS or AMD modules). 115 | 116 | 117 | ### Compatibility 118 | 119 | This library currently supports IE9+ and evergreen browsers. 120 | 121 | For browsers that do not support HTML imports (i.e. everything but Chrome), you will need to [polyfill it](https://github.com/webcomponents/webcomponentsjs): 122 | 123 | ```html 124 | 125 | 126 | ``` 127 | 128 | 129 | ### SystemJS 130 | 131 | Alternatively, you can use the SystemJS plugin by grabbing [a build](dist/sysjs-plugin) of it, renaming your build to `html.js` and placing that build somewhere on your load path for SystemJS to discover. 132 | 133 | You can then load HTML modules via: 134 | 135 | ```js 136 | // Implicit plugin name 137 | import { user, post } from './templates.html!' 138 | // Or explicit plugin name: 139 | import { user, post } from './templates.html!html' 140 | ``` 141 | 142 | 143 | ## Playing With It 144 | 145 | Try out the demo: 146 | 147 | ```sh 148 | gulp demo 149 | ``` 150 | 151 | 152 | ## Hacking On It 153 | 154 | Run this in the background for continuous testing/building: 155 | 156 | ```sh 157 | gulp 158 | ``` 159 | 160 | 161 | ## Understanding It 162 | 163 | For a deeper understanding of how HTML Exports works, take a look at [the annotated source](https://nevir.github.io/html-exports). 164 | -------------------------------------------------------------------------------- /demo/custom-elements-old/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Exports! 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 23 | 24 | 30 | 31 | 32 |

HTML Exports Custom Element Demo

33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/custom-elements-old/squid.js: -------------------------------------------------------------------------------- 1 | import { tentacle, ink } from './squidbits.html' 2 | 3 | export class Squid extends HTMLElement { 4 | attachedCallback() { 5 | this.name = this.tagName.toLowerCase() 6 | this.numTentacles = parseInt(this.getAttribute('tentacles') || 2) 7 | console.debug(this.name, '#attachedCallback') 8 | 9 | var root = this.createShadowRoot() 10 | 11 | var mantle = this.ownerDocument.createElement('div') 12 | mantle.textContent = '<' + this.name + '>' 13 | root.appendChild(mantle) 14 | 15 | root.appendChild(this.ownerDocument.importNode(ink.content, true)) 16 | for (var i = 0; i < this.numTentacles; i++) { 17 | root.appendChild(this.ownerDocument.importNode(tentacle.content, true)) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/custom-elements-old/squidbits.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/custom-elements/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Exports! 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 37 | 38 | 39 |

HTML Exports Custom Element Demo

40 | 41 |

42 | HTML exports gives us a ton of flexibility around how modules are organized. 43 | This demo serves as an example of organizational methods, by focusing on a 44 | custom element. 45 |

46 |

47 | Each example exports a prototype for the element, which we register directly 48 | via this document. 49 |

50 | 51 |

<squid-es5-combined>

52 |

53 | Both the element prototype and its templates are declared in the same file 54 | (squid-es5-combined.html), with an ES5 element definition. 55 |

56 | 57 | 58 |

<squid-es5-split>

59 |

60 | Another option is to structure your code as an ES5 module that imports its 61 | templates separately. See squid-es5-split.js for a CommonJS approach. 62 |

63 | 64 | 65 |

<squid-es6-combined>

66 |

67 | Both the element prototype and its templates are declared in the same file 68 | (squid-es6-combined.html), with an ES6 element definition. 69 |

70 | 71 | 72 |

<squid-es6-split>

73 |

74 | A third option is to declare your element as an ES6 module and class that 75 | imports its templates separately. See squid-es6-split.js for this approach. 76 |

77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /demo/custom-elements/squid-es5-combined.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /demo/custom-elements/squid-es5-split.js: -------------------------------------------------------------------------------- 1 | var squidbits = require('./squidbits.html') 2 | 3 | module.exports = Squid 4 | 5 | function Squid() {} 6 | Squid.prototype = Object.create(HTMLElement.prototype) 7 | 8 | Squid.prototype.attachedCallback = function attachedCallback() { 9 | this.name = this.tagName.toLowerCase() 10 | console.debug(this.name, '#attachedCallback') 11 | 12 | var root = this.createShadowRoot() 13 | // Note that `ink` and `tentacle` are made available to the scoped script 14 | // because they were exported (as a convenience). 15 | root.appendChild(this.ownerDocument.importNode(squidbits.ink.content, true)) 16 | for (var i = 0; i < 2; i++) { 17 | root.appendChild(this.ownerDocument.importNode(squidbits.tentacle.content, true)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/custom-elements/squid-es6-combined.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 28 | -------------------------------------------------------------------------------- /demo/custom-elements/squid-es6-split.js: -------------------------------------------------------------------------------- 1 | import { tentacle, ink } from './squidbits.html' 2 | 3 | export default class Squid extends HTMLElement { 4 | attachedCallback() { 5 | this.name = this.tagName.toLowerCase() 6 | console.debug(this.name, '#attachedCallback') 7 | 8 | var root = this.createShadowRoot() 9 | // Note that `ink` and `tentacle` are made available to the scoped script 10 | // because they were exported (as a convenience). 11 | root.appendChild(this.ownerDocument.importNode(ink.content, true)) 12 | for (var i = 0; i < 2; i++) { 13 | root.appendChild(this.ownerDocument.importNode(tentacle.content, true)) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/custom-elements/squidbits.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Exports! 7 | 8 | 9 |

HTML Exports Demos

10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/scoped-script/awesomesauce.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | alert('Awesomesauce!') 3 | } 4 | -------------------------------------------------------------------------------- /demo/scoped-script/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML Exports! 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 34 | 35 | 36 |

HTML Exports Scoped Scripts Demo

37 | 38 | 39 | -------------------------------------------------------------------------------- /demo/scoped-script/swoot.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /dist/html-exports.debug.js: -------------------------------------------------------------------------------- 1 | // # Internal Helpers 2 | 3 | ;(function(scope) { 4 | 'use strict' 5 | 6 | var _util = {} 7 | scope._util = _util 8 | 9 | // ## `flattenValueTuples` 10 | 11 | // After collecting value tuples ([key, value, source]), we can flatten them 12 | // into an value map, and also warn about any masked values. 13 | scope._util.flattenValueTuples = function flattenValueTuples(valueTuples, ignore) { 14 | var valueMap = {} 15 | var masked = [] 16 | for (var i = 0, tuple; tuple = valueTuples[i]; i++) { 17 | var key = tuple[0] 18 | if (key in valueMap && (!ignore || ignore.indexOf(key) === -1)) { 19 | masked.push(key) 20 | } 21 | valueMap[key] = tuple[1] 22 | } 23 | 24 | if (masked.length) { 25 | masked.forEach(_warnMaskedValues.bind(null, valueTuples)) 26 | } 27 | 28 | return valueMap 29 | } 30 | 31 | // It's important that we give authors as much info as possible to diagnose 32 | // any problems with their source. So, we spend a bit of computational effort 33 | // whenever values are masked (imports, exports, etc). 34 | function _warnMaskedValues(valueTuples, key) { 35 | var conflicting = valueTuples.filter(function(tuple) { 36 | return tuple[0] === key 37 | }).map(function(tuple) { 38 | return tuple[2] 39 | }) 40 | console.warn.apply(console, 41 | ['Multiple values named "' + key + '" were evaluated:'].concat(conflicting) 42 | ) 43 | } 44 | 45 | })(this.HTMLExports = this.HTMLExports || {}) 46 | 47 | // # LoaderHooks 48 | 49 | // `HTMLExports.LoaderHooks` is a mixable map of loader hooks that provide the 50 | // underlying behavior for loading HTML documents as modules. 51 | // 52 | // These hooks are designed to be consumed via various interfaces: 53 | // 54 | // * They are indirectly mixed into [`DocumentLoader`](documentloader.html). 55 | // 56 | // * They can be mixed into any existing loader via [`DocumentLoader.mixin`](documentloader.html#-documentloader-mixin-). 57 | // 58 | // * They can be used via a [SystemJS plugin](sysjs-plugin.html). 59 | // 60 | ;(function(scope) { 61 | 'use strict' 62 | 63 | var LoaderHooks = {} 64 | scope.LoaderHooks = LoaderHooks 65 | 66 | // ## `LoaderHooks.fetch` 67 | 68 | // Documents are fetched via a dynamic HTML import. This ensures that the 69 | // document's linked resources (stylesheets, scripts, etc) are also properly 70 | // loaded. 71 | // 72 | // The alternative would be for us to fetch document source and construct/load 73 | // HTML documents ourselves. This becomes rather complex, and would end up 74 | // duplicating much of the logic expressed by the HTML Imports polyfill. 75 | LoaderHooks.fetch = function fetchHTML(load) { 76 | console.debug('HTMLExports.LoaderHooks.fetch(', load, ')') 77 | 78 | return new Promise(function(resolve, reject) { 79 | var link = _newDynamicImport(load.address) 80 | 81 | link.addEventListener('error', function() { 82 | reject(new Error('Unknown failure when fetching URL: ' + load.address)) 83 | }) 84 | 85 | link.addEventListener('load', function() { 86 | // One problem with the module loader spec is that the `instantiate` 87 | // step does not support asynchronous execution. We want that, so that 88 | // we can ensure that any async-executed scripts in the document defer 89 | // its load (also so we can extract exports from them). 90 | // 91 | // Thus, we perform any async logic during load to emulate that (if 92 | // scoped scripts are enabled). 93 | var runScripts = scope.runScopedScripts && scope.runScopedScripts(link.import) || Promise.resolve() 94 | runScripts.then(function() { 95 | // Another difficulty of the module loader spec is that it rightfully 96 | // assumes that all sources are a `String`. Because we completely skip 97 | // over raw source, we need to be a little more crafty by placing the 98 | // real source in a side channel. 99 | load.metadata.importedHTMLDocument = link.import 100 | // And then, to appease the spec/SystemJS, we provide a dummy value for 101 | // `source`. 102 | resolve('') 103 | }) 104 | }) 105 | }) 106 | } 107 | 108 | // ## `LoaderHooks.instantiate` 109 | 110 | // Once we have a document fetched via HTML imports, we can extract the 111 | // its dependencies and exported values. 112 | // 113 | // However, it is worth noting that we gain the same document semantics as 114 | // HTML imports: stylesheets are merged into the root document, scripts 115 | // evaluate globally, etc. Good for simplifying code, not great for scoping. 116 | // 117 | // Furthermore, imports are not considered loaded until all of their linked 118 | // dependencies (stylesheets, scripts, etc) have also loaded. This makes 119 | // prefetching declared module dependencies difficult/impossible. 120 | LoaderHooks.instantiate = function instantiateHTML(load) { 121 | console.debug('HTMLExports.LoaderHooks.instantiate(', load, ')') 122 | var doc = load.metadata.importedHTMLDocument 123 | if (!doc) { 124 | throw new Error('HTMLExports bug: Expected fetched Document in metadata') 125 | } 126 | 127 | return { 128 | deps: scope.depsFor(doc).map(function(d) { return d.name }), 129 | execute: function executeHTML() { 130 | return this.newModule(scope.exportsFor(doc)) 131 | }.bind(this), 132 | } 133 | } 134 | 135 | // ## Document Processing 136 | 137 | // ### `HTMLExports.depsFor` 138 | 139 | // HTML modules can declare dependencies on any other modules via the `import` 140 | // element: 141 | // 142 | // ```html 143 | // 144 | // ``` 145 | scope.depsFor = function depsFor(document) { 146 | var declaredDependencies = document.querySelectorAll('import[src]') 147 | return Array.prototype.map.call(declaredDependencies, function(importNode) { 148 | // Much like ES6's import syntax, you can also choose which exported 149 | // values to bring into scope, and rename them. 150 | var aliases = {} 151 | // The default export can be imported via the `as` attribute: 152 | // 153 | // ```html 154 | // 155 | // ``` 156 | if (importNode.hasAttribute('as')) { 157 | aliases.default = importNode.getAttribute('as') 158 | } 159 | // Named exports can be imported via the `values` attribute (space 160 | // separated). 161 | // 162 | // ```html 163 | // 164 | // ``` 165 | if (importNode.hasAttribute('values')) { 166 | importNode.getAttribute('values').split(/\s+/).forEach(function(key) { 167 | if (key === '') return 168 | aliases[key] = key 169 | }) 170 | } 171 | 172 | // Each dependency returned is an object with: 173 | return { 174 | // * `name`: The declared name; you may want to `normalize` it. 175 | name: importNode.getAttribute('src'), 176 | // * `aliases`: A map of exported values to the keys they should be 177 | // exposed as. 178 | aliases: aliases, 179 | // * `source`: Source element, to aid in debugging. Expect a beating if 180 | // you leak this! 181 | source: importNode, 182 | } 183 | }) 184 | } 185 | 186 | // ### `HTMLExports.exportsFor` 187 | 188 | // HTML modules can export elements that are tagged with `export`. 189 | scope._exportTuplesFor = function _exportTuplesFor(document) { 190 | //- We collect [key, value, source] and then flatten at the end. 191 | var valueTuples = [] 192 | // They can either be named elements (via `id`), such as: 193 | // 194 | // ```html 195 | //
...
196 | // ``` 197 | var exportedNodes = document.querySelectorAll('[export][id]') 198 | for (var i = 0, node; node = exportedNodes[i]; i++) { 199 | valueTuples.push([node.getAttribute('id'), node, node]) 200 | } 201 | 202 | // Or they can be the default export when tagged with `default`: 203 | // 204 | // ```html 205 | //
...
206 | // ``` 207 | var defaultNodes = document.querySelectorAll('[export][default]') 208 | if (defaultNodes.length > 1) { 209 | throw new Error('Only one default export is allowed per document') 210 | } else if (defaultNodes.length === 1) { 211 | valueTuples.push(['default', defaultNodes[0], defaultNodes[0]]) 212 | // Otherwise, the default export will be the document. 213 | } else { 214 | valueTuples.push(['default', document, document]) 215 | } 216 | 217 | // Furthermore, values exported by ` 338 | // ``` 339 | var scripts = document.querySelectorAll('script[type="scoped"]') 340 | if (!scripts.length) { return resolve() } 341 | 342 | var exportMap = _resolveExports(allExports, deps, document) 343 | try { 344 | for (var i = 0, script; script = scripts[i]; i++) { 345 | _executeScript(exportMap, script) 346 | } 347 | // If any scripts fail to execute, we fail the entire load. 348 | } catch (error) { 349 | reject(error) 350 | } 351 | 352 | resolve() 353 | }) 354 | }) 355 | } 356 | 357 | // ## Internal Implementation 358 | 359 | // After we have loaded all the declared imports for a particular document, 360 | // we need to flatten the imported values into a set of (aliased) keys and 361 | // their values. 362 | function _resolveExports(allExports, deps, document) { 363 | console.assert(allExports.length === deps.length, 'declared dependencies do not match loaded exports') 364 | var valueTuples = [] 365 | for (var i = 0, dep; dep = deps[i]; i++) { 366 | var keys = Object.keys(dep.aliases) 367 | for (var j = 0, key; key = keys[j]; j++) { 368 | var alias = dep.aliases[key] 369 | if (!(key in allExports[i])) { 370 | console.warn('"' + key + '" was requested, but not exported from "' + dep.name + '".', dep.source) 371 | } 372 | valueTuples.push([alias, allExports[i][key], dep.source]) 373 | } 374 | } 375 | 376 | // As a convenience, we also expose values exported by the current document. 377 | valueTuples = valueTuples.concat(scope._exportTuplesFor(document)) 378 | var exportMap = scope._util.flattenValueTuples(valueTuples) 379 | // We have to be careful not to expose the document's default value though. 380 | delete exportMap.default 381 | 382 | return exportMap 383 | } 384 | 385 | // After all the imported values are flattened and validated, it is time to 386 | // execute the scoped scripts within the document. 387 | function _executeScript(exportMap, script) { 388 | // Scoped scripts follow the same lifecycle events as a regular ` 334 | // ``` 335 | var scripts = document.querySelectorAll('script[type="scoped"]') 336 | if (!scripts.length) { return resolve() } 337 | 338 | var exportMap = _resolveExports(allExports, deps, document) 339 | try { 340 | for (var i = 0, script; script = scripts[i]; i++) { 341 | _executeScript(exportMap, script) 342 | } 343 | // If any scripts fail to execute, we fail the entire load. 344 | } catch (error) { 345 | reject(error) 346 | } 347 | 348 | resolve() 349 | }) 350 | }) 351 | } 352 | 353 | // ## Internal Implementation 354 | 355 | // After we have loaded all the declared imports for a particular document, 356 | // we need to flatten the imported values into a set of (aliased) keys and 357 | // their values. 358 | function _resolveExports(allExports, deps, document) { 359 | var valueTuples = [] 360 | for (var i = 0, dep; dep = deps[i]; i++) { 361 | var keys = Object.keys(dep.aliases) 362 | for (var j = 0, key; key = keys[j]; j++) { 363 | var alias = dep.aliases[key] 364 | if (!(key in allExports[i])) { 365 | console.warn('"' + key + '" was requested, but not exported from "' + dep.name + '".', dep.source) 366 | } 367 | valueTuples.push([alias, allExports[i][key], dep.source]) 368 | } 369 | } 370 | 371 | // As a convenience, we also expose values exported by the current document. 372 | valueTuples = valueTuples.concat(scope._exportTuplesFor(document)) 373 | var exportMap = scope._util.flattenValueTuples(valueTuples) 374 | // We have to be careful not to expose the document's default value though. 375 | delete exportMap.default 376 | 377 | return exportMap 378 | } 379 | 380 | // After all the imported values are flattened and validated, it is time to 381 | // execute the scoped scripts within the document. 382 | function _executeScript(exportMap, script) { 383 | // Scoped scripts follow the same lifecycle events as a regular ` 24 | // ``` 25 | var scripts = document.querySelectorAll('script[type="scoped"]') 26 | if (!scripts.length) { return resolve() } 27 | 28 | var exportMap = _resolveExports(allExports, deps, document) 29 | try { 30 | for (var i = 0, script; script = scripts[i]; i++) { 31 | _executeScript(exportMap, script) 32 | } 33 | // If any scripts fail to execute, we fail the entire load. 34 | } catch (error) { 35 | reject(error) 36 | } 37 | 38 | resolve() 39 | }) 40 | }) 41 | } 42 | 43 | // ## Internal Implementation 44 | 45 | // After we have loaded all the declared imports for a particular document, 46 | // we need to flatten the imported values into a set of (aliased) keys and 47 | // their values. 48 | function _resolveExports(allExports, deps, document) { 49 | console.assert(allExports.length === deps.length, 'declared dependencies do not match loaded exports') 50 | var valueTuples = [] 51 | for (var i = 0, dep; dep = deps[i]; i++) { 52 | var keys = Object.keys(dep.aliases) 53 | for (var j = 0, key; key = keys[j]; j++) { 54 | var alias = dep.aliases[key] 55 | if (!(key in allExports[i])) { 56 | console.warn('"' + key + '" was requested, but not exported from "' + dep.name + '".', dep.source) 57 | } 58 | valueTuples.push([alias, allExports[i][key], dep.source]) 59 | } 60 | } 61 | 62 | // As a convenience, we also expose values exported by the current document. 63 | valueTuples = valueTuples.concat(scope._exportTuplesFor(document)) 64 | var exportMap = scope._util.flattenValueTuples(valueTuples) 65 | // We have to be careful not to expose the document's default value though. 66 | delete exportMap.default 67 | 68 | return exportMap 69 | } 70 | 71 | // After all the imported values are flattened and validated, it is time to 72 | // execute the scoped scripts within the document. 73 | function _executeScript(exportMap, script) { 74 | // Scoped scripts follow the same lifecycle events as a regular ` 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/resources/import.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixtures/resources/script.js: -------------------------------------------------------------------------------- 1 | window.SCRIPT_FIXTURE_LOADED = true 2 | -------------------------------------------------------------------------------- /test/fixtures/resources/style-import-nested.css: -------------------------------------------------------------------------------- 1 | .fixture-target {padding-bottom: 3px;} 2 | -------------------------------------------------------------------------------- /test/fixtures/resources/style-import.css: -------------------------------------------------------------------------------- 1 | .fixture-target {padding-right: 2px;} 2 | -------------------------------------------------------------------------------- /test/fixtures/resources/style-link.css: -------------------------------------------------------------------------------- 1 | @import "style-import-nested.css"; 2 | 3 | .fixture-target {padding-top: 1px;} 4 | -------------------------------------------------------------------------------- /test/fixtures/scoped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /test/fixtures/scopedexport.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /test/fixtures/simpledefault.html: -------------------------------------------------------------------------------- 1 | fizz 2 | nope 3 | -------------------------------------------------------------------------------- /test/fixtures/words.html: -------------------------------------------------------------------------------- 1 | Lorem 2 | ipsum 3 | dolor 4 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/shared/fixtures.js: -------------------------------------------------------------------------------- 1 | /* jshint -W098 */ 2 | 3 | function importFixtures(paths) { 4 | var fixtures = {} 5 | 6 | // Ideally this is not needed, but see: 7 | // https://github.com/ModuleLoader/es6-module-loader/issues/255 8 | var importOptions = {name: document.location.pathname} 9 | 10 | var imports = paths.map(function(path) { 11 | return System.import('../fixtures/' + path, importOptions).then(function(mod) { 12 | fixtures[path.replace(/\..+$/, '')] = mod 13 | }) 14 | }) 15 | 16 | // Block tests until all the fixtures have loaded 17 | before(function(done) { 18 | Promise.all(imports).then(function() { done() }) 19 | }) 20 | 21 | return fixtures 22 | } 23 | 24 | /* jshint +W098 */ 25 | -------------------------------------------------------------------------------- /test/unit/exports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/unit/resources.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/unit/scopedscript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 26 | 27 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /test/unit/systemjs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/unit/systemjs/thing.html: -------------------------------------------------------------------------------- 1 |
bar
2 | --------------------------------------------------------------------------------