├── .npmrc
├── cjs
├── package.json
└── index.js
├── .gitignore
├── .npmignore
├── test
├── compo-nent.js
├── what-ever.js
├── whatever-else.js
├── index.html
├── lazy.js
├── my-counter.uce
├── uce-lazy.js
└── my-counter.html
├── rollup
├── es.config.js
└── babel.config.js
├── es.js
├── min.js
├── LICENSE
├── esm
└── index.js
├── package.json
├── README.md
└── index.js
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .travis.yml
4 | node_modules/
5 | rollup/
6 | test/
7 |
--------------------------------------------------------------------------------
/test/compo-nent.js:
--------------------------------------------------------------------------------
1 | customElements.define('compo-nent', class extends HTMLElement {
2 | connectedCallback() {
3 | this.textContent = 'Hello loader!';
4 | }
5 | });
6 |
--------------------------------------------------------------------------------
/test/what-ever.js:
--------------------------------------------------------------------------------
1 | customElements.define('what-ever', class extends HTMLElement {
2 | connectedCallback() {
3 | this.innerHTML = `
4 | ${this.nodeName.toLowerCase()} is connected
5 |
6 | `;
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/test/whatever-else.js:
--------------------------------------------------------------------------------
1 | customElements.define('whatever-else', class extends HTMLElement {
2 | connectedCallback() {
3 | this.appendChild(
4 | this.ownerDocument.createElement('p')
5 | ).textContent = this.nodeName.toLowerCase() + ' is connected too';
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/rollup/es.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import {terser} from 'rollup-plugin-terser';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 |
8 | resolve({module: true}),
9 | terser()
10 | ],
11 |
12 | output: {
13 | exports: 'named',
14 | file: './es.js',
15 | format: 'iife',
16 | name: 'uceLoader'
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/rollup/babel.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import babel from 'rollup-plugin-babel';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 |
8 | resolve({module: true}),
9 | babel({presets: ['@babel/preset-env']})
10 | ],
11 |
12 | output: {
13 | exports: 'named',
14 | file: './index.js',
15 | format: 'iife',
16 | name: 'uceLoader'
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | uce-loader
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/es.js:
--------------------------------------------------------------------------------
1 | self.uceLoader=function(e){"use strict";const t=/^(?:annotation-xml|color-profile|font-face(?:|-format|-name|-src|-uri)|missing-glyph)$/i,n=new Set;return e.default=e=>{const o=e.container||document,r=o=>{for(let r=0,{length:c}=o;r{r([{addedNodes:e}])};s([document==o?o.documentElement:o]);const c=new MutationObserver(r);return c.observe(o,{subtree:!0,childList:!0}),c},e}({}).default;
2 |
--------------------------------------------------------------------------------
/min.js:
--------------------------------------------------------------------------------
1 | self.uceLoader=function(e){"use strict";var t=/^(?:annotation-xml|color-profile|font-face(?:|-format|-name|-src|-uri)|missing-glyph)$/i,n=new Set;return e.default=function(e){var r=e.container||document,o=function(r){for(var o=0,i=r.length;o
2 |
5 | {{state.count}}
6 |
9 |
10 |
11 |
24 |
25 |
46 |
--------------------------------------------------------------------------------
/test/uce-lazy.js:
--------------------------------------------------------------------------------
1 | addEventListener(
2 | 'DOMContentLoaded',
3 | function () {
4 | uceLoader({
5 | on: function (name) {
6 | if (name !== 'uce-template') {
7 | var self = this;
8 | if (!self.queue) {
9 | self.queue = [name];
10 | var script = document.createElement('script');
11 | script.src = '//unpkg.com/uce-template';
12 | document.body.appendChild(script).onload = function () {
13 | self.Template = customElements.get('uce-template');
14 | for (var i = 0; i < self.queue.length; i++)
15 | self.loader(self.queue[i]);
16 | };
17 | }
18 | else if (self.Template) {
19 | var xhr = new XMLHttpRequest;
20 | xhr.open('get', name + '.uce', true);
21 | xhr.send(null);
22 | xhr.onload = function () {
23 | document.body.appendChild(
24 | self.Template.from(xhr.responseText)
25 | );
26 | };
27 | }
28 | else
29 | self.queue.push(name);
30 | }
31 | }
32 | });
33 | },
34 | {once: true}
35 | );
36 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
2 | const ignore = /^(?:annotation-xml|color-profile|font-face(?:|-format|-name|-src|-uri)|missing-glyph)$/i;
3 | const loaded = new Set;
4 |
5 | /**
6 | * @typedef {Object} Options
7 | * @prop {Node} [container=document] - where to monitor Custom Elements
8 | * @prop {function} on - a callback invoked per each new Custom Element
9 | */
10 |
11 | /**
12 | * Start observing a document, or a specific container, and automatically
13 | * download once Custom Elements from a specific path.
14 | * @param {Options} options configuration options
15 | * @returns {MutationObserver} the disconnect-able `container` observer
16 | */
17 | export default (options) => {
18 | const target = options.container || document;
19 | const load = mutations => {
20 | for (let i = 0, {length} = mutations; i < length; i++) {
21 | for (let
22 | {addedNodes} = mutations[i],
23 | j = 0, {length} = addedNodes; j < length; j++
24 | ) {
25 | const node = addedNodes[j];
26 | const {children, getAttribute, tagName} = node;
27 | if (getAttribute) {
28 | const is = (getAttribute.call(node, 'is') || tagName).toLowerCase();
29 | if (0 < is.indexOf('-') && !loaded.has(is) && !ignore.test(is)) {
30 | loaded.add(is);
31 | customElements.get(is) || options.on(is);
32 | }
33 | crawl(children);
34 | }
35 | }
36 | }
37 | };
38 | const crawl = addedNodes => { load([{addedNodes}]) };
39 | crawl([document == target ? target.documentElement : target]);
40 | const observer = new MutationObserver(load);
41 | observer.observe(target, {subtree: true, childList: true});
42 | return observer;
43 | };
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uce-loader",
3 | "version": "2.0.0",
4 | "description": "A minimalistic, framework agnostic, lazy Custom Elements loader",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:babel && npm run min && npm run fix:default",
8 | "cjs": "ascjs --no-default esm cjs",
9 | "rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck",
10 | "rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck",
11 | "min": "terser index.js --comments='/^!/' -c -m -o min.js",
12 | "fix:default": "sed -i 's/({})/({}).default/' index.js && sed -i 's/({})/({}).default/' es.js && sed -i 's/({})/({}).default/' min.js"
13 | },
14 | "keywords": [
15 | "custom",
16 | "elements",
17 | "loader",
18 | "auto",
19 | "lazy"
20 | ],
21 | "author": "Andrea Giammarchi",
22 | "license": "ISC",
23 | "devDependencies": {
24 | "@babel/core": "^7.11.6",
25 | "@babel/preset-env": "^7.11.5",
26 | "ascjs": "^4.0.1",
27 | "rollup": "^2.28.2",
28 | "rollup-plugin-babel": "^4.4.0",
29 | "rollup-plugin-node-resolve": "^5.2.0",
30 | "rollup-plugin-terser": "^7.0.2",
31 | "terser": "^5.3.2"
32 | },
33 | "module": "./esm/index.js",
34 | "type": "module",
35 | "exports": {
36 | "import": "./esm/index.js",
37 | "default": "./cjs/index.js"
38 | },
39 | "unpkg": "min.js",
40 | "repository": {
41 | "type": "git",
42 | "url": "git+https://github.com/WebReflection/uce-loader.git"
43 | },
44 | "bugs": {
45 | "url": "https://github.com/WebReflection/uce-loader/issues"
46 | },
47 | "homepage": "https://github.com/WebReflection/uce-loader#readme"
48 | }
49 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
3 | const ignore = /^(?:annotation-xml|color-profile|font-face(?:|-format|-name|-src|-uri)|missing-glyph)$/i;
4 | const loaded = new Set;
5 |
6 | /**
7 | * @typedef {Object} Options
8 | * @prop {Node} [container=document] - where to monitor Custom Elements
9 | * @prop {function} on - a callback invoked per each new Custom Element
10 | */
11 |
12 | /**
13 | * Start observing a document, or a specific container, and automatically
14 | * download once Custom Elements from a specific path.
15 | * @param {Options} options configuration options
16 | * @returns {MutationObserver} the disconnect-able `container` observer
17 | */
18 | module.exports = (options) => {
19 | const target = options.container || document;
20 | const load = mutations => {
21 | for (let i = 0, {length} = mutations; i < length; i++) {
22 | for (let
23 | {addedNodes} = mutations[i],
24 | j = 0, {length} = addedNodes; j < length; j++
25 | ) {
26 | const node = addedNodes[j];
27 | const {children, getAttribute, tagName} = node;
28 | if (getAttribute) {
29 | const is = (getAttribute.call(node, 'is') || tagName).toLowerCase();
30 | if (0 < is.indexOf('-') && !loaded.has(is) && !ignore.test(is)) {
31 | loaded.add(is);
32 | customElements.get(is) || options.on(is);
33 | }
34 | crawl(children);
35 | }
36 | }
37 | }
38 | };
39 | const crawl = addedNodes => { load([{addedNodes}]) };
40 | crawl([document == target ? target.documentElement : target]);
41 | const observer = new MutationObserver(load);
42 | observer.observe(target, {subtree: true, childList: true});
43 | return observer;
44 | };
45 |
--------------------------------------------------------------------------------
/test/my-counter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | uce-template lazy load example
7 |
8 |
45 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # µce-loader
2 |
3 | **Social Media Photo by [Guillaume Bolduc](https://unsplash.com/@guibolduc) on [Unsplash](https://unsplash.com/)**
4 |
5 | A minimalistic, framework agnostic, lazy Custom Elements loader.
6 |
7 | ### Example
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 |
15 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | If `loader({container: document, on(tagName){}})` API is too simplified, feel free to check [lazytag](https://github.com/WebReflection/lazytag#readme) out.
45 |
46 | ### About ShadowDOM
47 |
48 | If your components use `attachShadow` and internally use custom elements that should be lazy loaded, be sure the `shadowRoot` is observed.
49 |
50 | ```js
51 | const shadowRoot = this.attachShadow({mode: any});
52 | loader({
53 | container: shadowRoot,
54 | on(newTag) {
55 | // ... load components
56 | }
57 | });
58 | ```
59 |
60 | ### V2 vs V1
61 |
62 | Current version of this module does *not* invoke the `.on(...)` method if the element is already registered as Custom Element.
63 |
64 | In *V1* any tag name would've passed through the loader instead.
65 |
66 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | self.uceLoader = (function (exports) {
2 | 'use strict';
3 |
4 | // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
5 | var ignore = /^(?:annotation-xml|color-profile|font-face(?:|-format|-name|-src|-uri)|missing-glyph)$/i;
6 | var loaded = new Set();
7 | /**
8 | * @typedef {Object} Options
9 | * @prop {Node} [container=document] - where to monitor Custom Elements
10 | * @prop {function} on - a callback invoked per each new Custom Element
11 | */
12 |
13 | /**
14 | * Start observing a document, or a specific container, and automatically
15 | * download once Custom Elements from a specific path.
16 | * @param {Options} options configuration options
17 | * @returns {MutationObserver} the disconnect-able `container` observer
18 | */
19 |
20 | var index = (function (options) {
21 | var target = options.container || document;
22 |
23 | var load = function load(mutations) {
24 | for (var i = 0, length = mutations.length; i < length; i++) {
25 | for (var addedNodes = mutations[i].addedNodes, j = 0, _length = addedNodes.length; j < _length; j++) {
26 | var node = addedNodes[j];
27 | var children = node.children,
28 | getAttribute = node.getAttribute,
29 | tagName = node.tagName;
30 |
31 | if (getAttribute) {
32 | var is = (getAttribute.call(node, 'is') || tagName).toLowerCase();
33 |
34 | if (0 < is.indexOf('-') && !loaded.has(is) && !ignore.test(is)) {
35 | loaded.add(is);
36 | customElements.get(is) || options.on(is);
37 | }
38 |
39 | crawl(children);
40 | }
41 | }
42 | }
43 | };
44 |
45 | var crawl = function crawl(addedNodes) {
46 | load([{
47 | addedNodes: addedNodes
48 | }]);
49 | };
50 |
51 | crawl([document == target ? target.documentElement : target]);
52 | var observer = new MutationObserver(load);
53 | observer.observe(target, {
54 | subtree: true,
55 | childList: true
56 | });
57 | return observer;
58 | });
59 |
60 | exports.default = index;
61 |
62 | return exports;
63 |
64 | }({}).default);
65 |
--------------------------------------------------------------------------------