├── .gitignore
├── src
├── shadow-dom.js
├── shadow-dom-css.js
├── html-imports.js
├── document.currentScript.js
└── document.registerElement.js
├── test
├── shadow-dom-css.html
├── nested-html-imports.html
├── components
│ ├── shadow-dom-css.html
│ ├── test1.html
│ └── sub-import1.html
└── index.html
├── package.json
├── Gruntfile.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | *.sublime-*
4 |
--------------------------------------------------------------------------------
/src/shadow-dom.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | if ('createShadowRoot' in HTMLElement.prototype) { return; }
4 |
5 | HTMLElement.prototype.createShadowRoot = function() {
6 |
7 | return this.shadowRoot = this;
8 | };
9 |
10 | })();
11 |
--------------------------------------------------------------------------------
/test/shadow-dom-css.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/nested-html-imports.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-components-polyfills",
3 | "version": "1.2.1",
4 | "description": "Lightweight Web Components polyfills.",
5 | "main": "web-components-polyfills.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git@git.dev.wds.co:web-components/web-components-polyfills.git"
12 | },
13 | "keywords": [
14 | "web",
15 | "components",
16 | "shadow",
17 | "dom",
18 | "custom",
19 | "elements",
20 | "html",
21 | "imports"
22 | ],
23 | "author": "Roman Liutikov ",
24 | "license": "MIT",
25 | "dependencies": {},
26 | "devDependencies": {
27 | "grunt": "^0.4.5",
28 | "grunt-contrib-uglify": "^0.5.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (grunt) {
4 |
5 | grunt.initConfig({
6 | pkg: grunt.file.readJSON('package.json'),
7 | uglify: {
8 | optimize: {
9 | files: {
10 | 'build/web-components-polyfills.min.js': [
11 | 'src/document.currentScript.js',
12 | 'src/shadow-dom-css.js',
13 | 'src/shadow-dom.js',
14 | 'src/document.registerElement.js',
15 | 'src/html-imports.js'
16 | ]
17 | },
18 | options: {
19 | wrap: true,
20 | sourceMap: true,
21 | sourceMapName: 'build/web-components-polyfills.min.js.map'
22 | }
23 | },
24 | options: {
25 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
26 | '<%= grunt.template.today("yyyy-mm-dd") %> */'
27 | }
28 | }
29 | });
30 |
31 | grunt.loadNpmTasks('grunt-contrib-uglify');
32 |
33 | grunt.registerTask('default', ['uglify:optimize']);
34 |
35 | };
36 |
--------------------------------------------------------------------------------
/test/components/shadow-dom-css.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 | Hey you!
22 |
23 |
24 |
25 |
50 |
--------------------------------------------------------------------------------
/test/components/test1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
50 |
--------------------------------------------------------------------------------
/test/components/sub-import1.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
18 |
19 | Imma ur pappa elemente!
20 |
21 |
22 |
23 |
24 |
25 |
26 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/shadow-dom-css.js:
--------------------------------------------------------------------------------
1 | (function (scope) {
2 |
3 | /* Assume Shadow DOM is supported */
4 | scope.ShadowDOMCSS = { support: true };
5 |
6 | /* Check for Shadow DOM support */
7 | if ('createShadowRoot' in HTMLElement.prototype) { return; }
8 |
9 | /* If not — set to false */
10 | scope.ShadowDOMCSS.support = false;
11 |
12 | /* Shadow DOM CSS polyfill */
13 | scope.ShadowDOMCSS.shim = function (importEl) {
14 |
15 | var importStyles;
16 |
17 | /* Get styles from template. Depends on element support */
18 | if ('HTMLTemplateElement' in window) {
19 |
20 | importStyles = importEl.import.body.querySelector('template')
21 | .content.querySelectorAll('style')
22 | } else {
23 |
24 | importStyles = importEl.import.body.querySelectorAll('style');
25 | }
26 |
27 | /* Process styles */
28 | Array.prototype.forEach.call(importStyles, function (importStyle) {
29 |
30 | /* Get custom element tag name */
31 | var scopeSelector = importStyle.textContent
32 | .match(/\/\*! *([a-z,-]+) *\*\//)[1];
33 |
34 | /* Get host selector */
35 | var hostSelectorFn = importStyle.textContent.match(/(:host)(\((.*)\))/);
36 |
37 | /* Convert host selector */
38 | if (hostSelectorFn) {
39 |
40 | importStyle.textContent =
41 | importStyle.textContent.replace(RegExp(hostSelectorFn[1], 'g'), scopeSelector);
42 |
43 | importStyle.textContent =
44 | importStyle.textContent.replace(
45 | RegExp(hostSelectorFn[2].replace(/[()]/g, '\\$&'), 'g'), hostSelectorFn[3]);
46 |
47 | } else {
48 |
49 | importStyle.textContent =
50 | importStyle.textContent.replace(/:host/, scopeSelector);
51 | }
52 |
53 | /* Encapsulate styles with scope selector */
54 | var cssText = '';
55 |
56 | document.head.appendChild(importStyle);
57 |
58 | Array.prototype.forEach.call(importStyle.sheet.cssRules, function (rule) {
59 |
60 | var scopedSelector = rule.selectorText;
61 |
62 | if (!scopedSelector.match(scopeSelector)) {
63 |
64 | scopedSelector = scopeSelector + ' ' + scopedSelector;
65 | }
66 |
67 | cssText += scopedSelector + ' { ' + rule.style.cssText + ' } ';
68 | });
69 |
70 | /* Apply polyfilled styles */
71 | importStyle.textContent = cssText;
72 | });
73 | };
74 | })(window);
75 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/html-imports.js:
--------------------------------------------------------------------------------
1 | (function (scope) {
2 |
3 | /* Do not apply polyfill if HTML Imports is supported */
4 | if ('import' in document.createElement('link')) { return; }
5 |
6 | /* Process each import */
7 | Array.prototype.forEach.call(document.querySelectorAll('link[rel="import"]'), function (importEl) {
8 |
9 | // var importEl = importElement;
10 |
11 | /* Fetch import */
12 | var xhr = new XMLHttpRequest();
13 | xhr.open('GET', importEl.getAttribute('href') && importEl.getAttribute('href'));
14 | xhr.onload = function() {
15 |
16 | if (xhr.status === 200) {
17 |
18 | /* Create and store document */
19 | importEl.import = document.implementation.createHTMLDocument('import');
20 | importEl.import.body.innerHTML = xhr.responseText;
21 |
22 | /* Shim Shadow DOM CSS selectors if doesn't supported */
23 | if (!scope.ShadowDOMCSS.support) { scope.ShadowDOMCSS.shim(importEl); }
24 |
25 | /* Handle JavaScript from import document.
26 | * Load and execute external scripts first, then inline */
27 | var importScripts = importEl.import.body.querySelectorAll('script'),
28 | loadingScripts = 0,
29 | inlineScriptsCache = [];
30 |
31 | Array.prototype.forEach.call(importScripts, function (importScript) {
32 |
33 | var scriptSrc = importScript.getAttribute('src');
34 |
35 | /* Handle external script */
36 | if (scriptSrc) {
37 |
38 | /* Create execution stack for following inline scripts */
39 | inlineScriptsCache.push([]);
40 |
41 | loadingScripts++;
42 |
43 | var script = document.createElement('script');
44 | script.src = scriptSrc;
45 |
46 | /* As soon as external script loaded,
47 | * execute following stack of inline scripts */
48 | script.onload = function() {
49 |
50 | var callStack = inlineScriptsCache[
51 | inlineScriptsCache.length - loadingScripts];
52 |
53 | for (var i = 0, ln = callStack.length; i < ln; i++) {
54 |
55 | handleInlineScript(importEl,
56 | importScripts, callStack[i]);
57 | }
58 |
59 | loadingScripts--;
60 | };
61 |
62 | document.head.appendChild(script);
63 | }
64 |
65 | /* Handle inline script */
66 | else {
67 |
68 | /* Execute script if no external loading */
69 | if (!loadingScripts) {
70 |
71 | handleInlineScript(importEl, importScripts, importScript);
72 | }
73 |
74 | /* Put script into current execution stack */
75 | else {
76 |
77 | inlineScriptsCache[loadingScripts - 1].push(importScript);
78 | }
79 | }
80 | });
81 |
82 | }
83 | };
84 |
85 | xhr.send();
86 | });
87 |
88 | function handleInlineScript (importEl, importScripts, importScript) {
89 |
90 | var script = document.createElement('script');
91 |
92 | script.currentScript = importScript;
93 | script.import = importEl.import;
94 |
95 | if ('btoa' in window) {
96 |
97 | script.src = 'data:text/javascript;base64,' +
98 | btoa(script.currentScript.textContent);
99 | } else {
100 |
101 | script.src = 'data:text/javascript;charset=utf-8,' +
102 | encodeURIComponent(script.currentScript.textContent);
103 | }
104 |
105 | script.onload = function() { this.parentNode.removeChild(script); };
106 |
107 | document.head.appendChild(script);
108 | }
109 | })(window);
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lightweight Web Components polyfills
2 |
3 | 
4 |
5 | `build/web-components-polyfills.min.js` lightly polyfills HTML Imports, Shadow DOM (including CSS selectors), `document.createElement` and `document.currentScript`.
6 |
7 | With this polyfill it is possible to create lightweight and compatible (mobile & IE9+) Web Components according to W3C standards, so the code will run great in both outdated and modern browsers versions.
8 |
9 | ## Usage
10 |
11 | Include polyfill on the page and import your components using `` element.
12 |
13 | ```
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ```
29 |
30 | ## Creating web components
31 |
32 | Name your component, for example `custom-element`. Create HTML file `custom-element.html`.
33 |
34 | ### Template
35 |
36 | There's a `template` HTML element which stores component's template:
37 |
38 | ```
39 |
40 |
41 |
49 |
50 | Hola!
51 |
52 |
53 | ```
54 |
55 | ### Styles
56 |
57 | Currently Shadow DOM CSS Polyfill can process only styles inside of `