Skip to content

Commit 4b02a84

Browse files
mnemanjaLukasBombach
authored andcommitted
Add support for opting-out from shadowRoot behavior. (LukasBombach#6)
* Add support for opting-out from shadowRoot behavior. * Adjust the Readme.md. * Bump the version. * Add .editorsconfig file. * Added type definition and dev dependencies * Transpile from ES6 to ES5 * Add support for attributes to props and move the react-dom to peerDependency * Remove source folder since it's not needed.
1 parent 726322a commit 4b02a84

12 files changed

+2028
-152
lines changed

.babelrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"presets": [
3+
["env", {
4+
"targets": {
5+
"browsers": ["last 2 versions", "safari >= 7"]
6+
}
7+
}]
8+
],
9+
"env": {
10+
"production": {
11+
"presets": ["minify"]
12+
}
13+
}
14+
}

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ Then in your HTML simply use your web component, in this case named `my-componen
4545
<my-component></my-component>
4646
```
4747

48+
By default the `shadowRoot` is enabled. This allows for styles isolation and prevents component styles from
49+
*bleeding out* to other parts of the application. It also prevents outer styles from affecting the web component you are creating.
50+
51+
In case that you want your component to inherit styles from the parent you can opt-out of the shadowRoot.
52+
To do that you can pass an **optional** parameter to the `create` method:
53+
```js
54+
ReactWebComponent.create(<App />, 'my-component', true);
55+
```
56+
57+
4858
It is also possible to create multiple web components in a single project and pass on props:
4959

5060
```javascript

index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare namespace ReactWebComponent {
2+
export const create: (app: JSX.Element, tagName: string, optOutFromShadowRoot?: boolean) => void;
3+
}
4+
5+
export default ReactWebComponent;

package.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
{
22
"name": "react-web-component",
3-
"version": "1.0.14",
3+
"version": "1.2.0",
44
"description": "Create Web Components with React",
55
"main": "src/index.js",
6+
"types": "index.d.ts",
67
"author": "Lukas Bombach <[email protected]>",
78
"license": "MIT",
89
"repository": "WeltN24/react-web-component",
10+
"scripts": {
11+
"build": "babel src/dev -d src --presets minify"
12+
},
913
"dependencies": {
10-
"react-dom": "^16.0.0",
1114
"react-shadow-dom-retarget-events": "^1.0.8"
15+
},
16+
"peerDependencies": {
17+
"react-dom": "^16.2.0"
18+
},
19+
"devDependencies": {
20+
"@types/react": "^16.0.38",
21+
"babel-cli": "^6.26.0",
22+
"babel-minify": "^0.3.0",
23+
"babel-preset-env": "^1.6.1",
24+
"babel-preset-minify": "^0.3.0"
1225
}
1326
}

src/dev/extractAttributes.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Takes in a node attributes map and returns an object with camelCased properties and values
3+
* @param nodeMap
4+
* @returns {{}}
5+
*/
6+
module.exports = function extractAttributes(nodeMap) {
7+
if (!nodeMap.attributes) {
8+
return {};
9+
}
10+
11+
let obj = {};
12+
let attribute;
13+
const attributesAsNodeMap = [...nodeMap.attributes];
14+
const attributes = attributesAsNodeMap.map((attribute) => ({ [attribute.name]: attribute.value }));
15+
16+
for (attribute of attributes) {
17+
const key = Object.keys(attribute)[0];
18+
const camelCasedKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
19+
obj[camelCasedKey] = attribute[key];
20+
}
21+
22+
return obj;
23+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* An optional library which is conditionally added
3+
* @returns {[]}
4+
*/
5+
module.exports = () => {
6+
try {
7+
return require('react-web-component-style-loader/exports').styleElements;
8+
} catch (e) {
9+
return [];
10+
}
11+
};

src/dev/index.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const ReactDOM = require('react-dom');
2+
const retargetEvents = require('react-shadow-dom-retarget-events');
3+
const getStyleElementsFromReactWebComponentStyleLoader = require('./getStyleElementsFromReactWebComponentStyleLoader');
4+
const extractAttributes = require('./extractAttributes');
5+
6+
module.exports = {
7+
/**
8+
* @param {JSX.Element} app
9+
* @param {string} tagName - The name of the web component. Has to be minus "-" delimited.
10+
* @param {boolean} useShadowDom - If the value is set to "true" the web component will use the `shadowDom`. The default value is true.
11+
*/
12+
create: (app, tagName, useShadowDom = true) => {
13+
let appInstance;
14+
15+
const lifeCycleHooks = {
16+
attachedCallback: 'webComponentAttached',
17+
connectedCallback: 'webComponentConnected',
18+
disconnectedCallback: 'webComponentDisconnected',
19+
attributeChangedCallback: 'webComponentAttributeChanged',
20+
adoptedCallback: 'webComponentAdopted'
21+
};
22+
23+
function callConstructorHook(webComponentInstance) {
24+
if (appInstance['webComponentConstructed']) {
25+
appInstance['webComponentConstructed'].apply(appInstance, [webComponentInstance])
26+
}
27+
}
28+
29+
function callLifeCycleHook(hook, params) {
30+
const instanceParams = params || [];
31+
const instanceMethod = lifeCycleHooks[hook];
32+
33+
if (instanceMethod && appInstance && appInstance[instanceMethod]) {
34+
appInstance[instanceMethod].apply(appInstance, instanceParams);
35+
}
36+
}
37+
38+
const proto = Object.create(HTMLElement.prototype, {
39+
attachedCallback: {
40+
value: function() {
41+
let webComponentInstance = this;
42+
let mountPoint = webComponentInstance;
43+
44+
if (useShadowDom) {
45+
// Re-assign the webComponentInstance (this) to the newly created shadowRoot
46+
webComponentInstance = webComponentInstance.createShadowRoot();
47+
48+
// Re-assign the mountPoint to the newly created "div" element
49+
mountPoint = document.createElement('div');
50+
51+
// Move all of the styles assigned to the react component inside of the shadowRoot.
52+
// By default this is not used, only if the library is explicitly installed
53+
const styles = getStyleElementsFromReactWebComponentStyleLoader();
54+
styles.forEach((style) => {
55+
webComponentInstance.appendChild(style.cloneNode(webComponentInstance));
56+
});
57+
58+
webComponentInstance.appendChild(mountPoint);
59+
60+
retargetEvents(webComponentInstance);
61+
}
62+
63+
ReactDOM.render(app, mountPoint, function () {
64+
appInstance = this;
65+
appInstance.props = extractAttributes(webComponentInstance);
66+
67+
callConstructorHook(webComponentInstance);
68+
callLifeCycleHook('attachedCallback');
69+
});
70+
},
71+
},
72+
connectedCallback: {
73+
value: () => {
74+
callLifeCycleHook('connectedCallback');
75+
},
76+
},
77+
disconnectedCallback: {
78+
value: () => {
79+
callLifeCycleHook('disconnectedCallback');
80+
},
81+
},
82+
attributeChangedCallback: {
83+
value: (attributeName, oldValue, newValue, namespace) => {
84+
callLifeCycleHook('attributeChangedCallback', [attributeName, oldValue, newValue, namespace]);
85+
},
86+
},
87+
adoptedCallback: {
88+
value: (oldDocument, newDocument) => {
89+
callLifeCycleHook('adoptedCallback', [oldDocument, newDocument]);
90+
},
91+
},
92+
});
93+
94+
document.registerElement(tagName, { prototype: proto });
95+
},
96+
};

src/extractAttributes.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/getStyleElementsFromReactWebComponentStyleLoader.js

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.js

Lines changed: 1 addition & 79 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)