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 |
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 |
6 |
9 |
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 |
6 |
9 |
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 |
2 |
5 |
6 |
7 | ✗
8 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HTML Exports!
7 |
8 |
9 |
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 |