Skip to content

Commit e4bd0a9

Browse files
committed
Initial commit
0 parents  commit e4bd0a9

11 files changed

+416
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
examples/client-only/bundle.js
3+
examples/client-only/node_modules/
4+
examples/server-rendering/bundle.js
5+
examples/server-rendering/node_modules/

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# react-stylesheet
2+
3+
A component for React to inject stylesheets links into document's head.
4+
5+
## Installation
6+
7+
% npm install react-stylsheets
8+
9+
## Usage
10+
11+
Use `<Stylesheet />` component to declare stylesheets:
12+
13+
var React = require('react');
14+
var Stylesheet = require('react-stylesheet');
15+
16+
var App = React.createClass({
17+
render: function() {
18+
return (
19+
<div>
20+
<Stylesheet href="/assets/app.css" />
21+
<Button />
22+
</div>
23+
);
24+
}
25+
});
26+
27+
var Button = React.createClass({
28+
render: function() {
29+
return (
30+
<a>
31+
<Stylesheet href="/assets/widgets/button.css" />
32+
...
33+
</a>
34+
);
35+
}
36+
});
37+
38+
After rendering the component hierarchy, all declared stylesheets will be
39+
inserted into document's head.
40+
41+
If you use fullpage rendering and prerender your UI on server with
42+
`React.renderComponentToString(...)`, then all the `<link>` tags will be
43+
rendered inside the `<head>` tag.

ReactStylesheet.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"use strict";
2+
3+
var React = require('react');
4+
var ReactMarkupChecksum = require('react/lib/ReactMarkupChecksum');
5+
var RootCommunication = require('./RootCommunication');
6+
var StylesheetImage = require('./StylesheetImage');
7+
var renderComponent = require('./renderComponent');
8+
9+
var ReactStylesheet = React.createClass({
10+
11+
displayName: 'ReactStylesheet',
12+
13+
mixins: [RootCommunication],
14+
15+
getImage: function() {
16+
var element = document.head.querySelector('link[href="' + this.props.href + '"]');
17+
if (!element) {
18+
return;
19+
}
20+
var references = element.getAttribute('data-references');
21+
if (!references) {
22+
return;
23+
}
24+
return {
25+
element: element,
26+
references: parseInt(references, 10),
27+
remove: function() {
28+
document.head.removeChild(element);
29+
},
30+
setReferences: function(num) {
31+
element.setAttribute('data-references', num);
32+
}
33+
};
34+
},
35+
36+
shouldComponentUpdate: function(nextProps) {
37+
return nextProps.href !== this.props.href;
38+
},
39+
40+
componentWillUnmount: function() {
41+
var image = this.getImage();
42+
if (!image) {
43+
return;
44+
}
45+
if (image.references === 1) {
46+
image.remove();
47+
} else {
48+
image.setReferences(image.references - 1);
49+
}
50+
},
51+
52+
componentDidMount: function() {
53+
if (!renderComponent.renderHappened &&
54+
document.documentElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME)) {
55+
return;
56+
}
57+
58+
var image = this.getImage();
59+
if (!image) {
60+
var element = StylesheetImage.createElement('', this.props.href, 1);
61+
if (document.head.firstChild) {
62+
document.head.insertBefore(element, document.head.firstChild);
63+
} else {
64+
document.head.appendChild(element);
65+
}
66+
} else {
67+
image.setReferences(image.references + 1);
68+
}
69+
},
70+
71+
componentWillUpdate: function() {
72+
this.componentWillUnmount();
73+
},
74+
75+
componentDidUpdate: function() {
76+
this.componentDidMount();
77+
},
78+
79+
render: function() {
80+
if (typeof window === 'undefined') {
81+
var root = this.getRootComponent();
82+
var stylesheets = root.__stylesheets = root.__stylesheets || {};
83+
stylesheets[this.props.href] = (stylesheets[this.props.href] || 0) + 1
84+
}
85+
return React.DOM.noscript({key: this.props.href});
86+
}
87+
});
88+
89+
module.exports = ReactStylesheet;

ReactStylesheetHead.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use strict";
2+
3+
var React = require('react');
4+
var head = React.DOM.head;
5+
var RootCommunication = require('./RootCommunication');
6+
7+
/**
8+
* Component which hosts stylesheet images.
9+
*/
10+
var ReactStylesheetHead = React.createClass({
11+
12+
displayName: 'ReactStylesheetHead',
13+
14+
mixins: [RootCommunication],
15+
16+
render: function() {
17+
if (typeof window === 'undefined') {
18+
var root = this.getRootComponent();
19+
root.__stylesheets_headNodeID = this._rootNodeID;
20+
}
21+
var links = typeof document !== 'undefined' ?
22+
readStylesheetImagesFromDOM() :
23+
[];
24+
return this.transferPropsTo(head(null, links.concat(this.props.children)));
25+
}
26+
});
27+
28+
/**
29+
* Read stylesheet images from DOM.
30+
*
31+
* @private
32+
*/
33+
function readStylesheetImagesFromDOM() {
34+
var links = document.querySelectorAll('link[data-references]');
35+
return Array.prototype.map.call(links, function(node) {
36+
var href = node.getAttribute('href');
37+
return React.DOM.link({
38+
'data-references': node.getAttribute('data-references'),
39+
href: href,
40+
key: href
41+
});
42+
});
43+
}
44+
45+
module.exports = ReactStylesheetHead;

RootCommunication.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use strict";
2+
3+
/**
4+
* Provide `getRootComponent()` method to acquire a reference to the root
5+
* component.
6+
*/
7+
var RootCommunication = {
8+
9+
getRootComponent: function() {
10+
var root = this;
11+
while (root._owner) root = root._owner;
12+
return root;
13+
}
14+
};
15+
16+
module.exports = RootCommunication;

StylesheetImage.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use strict";
2+
3+
var escapeTextForBrowser = require('react/lib/escapeTextForBrowser');
4+
var wrapUserProvidedKey = require('./wrapUserProvidedKey');
5+
6+
/**
7+
* Create markup for stylesheet image.
8+
*
9+
* @private
10+
*
11+
* @param {String} rootNodeID
12+
* @param {String} href
13+
* @param {String} references
14+
*/
15+
function createMarkup(rootNodeID, href, references) {
16+
var key = escapeTextForBrowser(wrapUserProvidedKey(href));
17+
return (
18+
'<link rel="stylesheet" href="' + href + '"' +
19+
' data-reactid="' + rootNodeID + '.' + key + '"' +
20+
' data-references="' + references + '"' + '></link>'
21+
);
22+
}
23+
24+
function createElement(rootNodeID, href, references) {
25+
var markup = createMarkup(rootNodeID, href, references);
26+
var container = document.createElement('head');
27+
container.innerHTML = markup;
28+
return container.children[0];
29+
}
30+
31+
module.exports = {
32+
createMarkup: createMarkup,
33+
createElement: createElement
34+
};

index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
var React = require('react');
2+
var renderComponentToString = require('./renderComponentToString');
3+
var renderComponent = require('./renderComponent');
4+
var ReactStylesheetHead = require('./ReactStylesheetHead');
5+
var ReactStylesheet = require('./ReactStylesheet');
6+
7+
React.renderComponent = renderComponent;
8+
React.renderComponentToString = renderComponentToString;
9+
React.DOM.head = ReactStylesheetHead;
10+
11+
module.exports = ReactStylesheet;
12+
module.exports.Stylesheet = ReactStylesheet;
13+
module.exports.StylesheetHead = ReactStylesheetHead;
14+
module.exports.renderComponentToString = renderComponentToString;
15+
module.exports.renderComponent = renderComponent;

package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "react-stylesheet",
3+
"version": "0.0.0",
4+
"description": "A component for React to inject stylesheets links into document's head",
5+
"main": "index.js",
6+
"dependencies": {
7+
"react": "~0.9.0-alpha",
8+
"envify": "~0.2.0"
9+
},
10+
"devDependencies": {},
11+
"scripts": {
12+
"test": "make test"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "git://github.com/andreypopp/react-stylesheet"
17+
},
18+
"keywords": [
19+
"react-component",
20+
"stylesheet"
21+
],
22+
"author": "Andrey Popp <8mayday@gmail.com>",
23+
"license": "MIT",
24+
"bugs": {
25+
"url": "https://github.com/andreypopp/react-stylesheet/issues"
26+
},
27+
"homepage": "https://github.com/andreypopp/react-stylesheet"
28+
}

renderComponent.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use strict";
2+
3+
var React = require('react');
4+
5+
var renderComponentImpl = React.renderComponent;
6+
7+
var renderHappened = false;
8+
9+
/**
10+
* We set a special flat which instructs ReactStylesheet components to ignore
11+
* stylesheets reference counts on componentDidMount callback if there's markup
12+
* from server side rendering left.
13+
*/
14+
function renderComponent(component, element) {
15+
component = renderComponentImpl(component, element);
16+
if (!renderHappened) {
17+
renderHappened = true;
18+
}
19+
return component;
20+
}
21+
22+
module.exports = renderComponent;
23+
module.exports.renderHappened = renderHappened;

renderComponentToString.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use strict";
2+
3+
var React = require('react');
4+
var ReactMarkupChecksum = require('react/lib/ReactMarkupChecksum');
5+
var StylesheetImage = require('./StylesheetImage');
6+
7+
var renderComponentToStringImpl = React.renderComponentToString;
8+
9+
/**
10+
* Render component to string
11+
*
12+
* @param {Component} component
13+
*/
14+
function renderComponentToString(component) {
15+
var markup = renderComponentToStringImpl(component);
16+
17+
if (component.__stylesheets_headNodeID && component.__stylesheets) {
18+
markup = injectStylesheetImages(
19+
markup,
20+
component.__stylesheets,
21+
component.__stylesheets_headNodeID
22+
);
23+
markup = stripChecksumFromMarkup(markup);
24+
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
25+
}
26+
return markup;
27+
28+
}
29+
30+
/**
31+
* Strip checksum from markup.
32+
*
33+
* @private
34+
*
35+
* @param {String} markup
36+
*/
37+
function stripChecksumFromMarkup(markup) {
38+
return markup.replace(
39+
new RegExp(' ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"'),
40+
'');
41+
}
42+
43+
/**
44+
* Inject stylesheet images into markup.
45+
*
46+
* @private
47+
*
48+
* @param {String} markup
49+
* @param {Object} stylesheets
50+
* @param {String} headNodeID
51+
*/
52+
function injectStylesheetImages(markup, stylesheets, headNodeID) {
53+
var injection = [];
54+
for (var href in stylesheets)
55+
injection.push(StylesheetImage.createMarkup(headNodeID, href, stylesheets[href]));
56+
injection = injection.join('');
57+
markup = markup.replace(/<head[^>]*>/, function(m) { return m + injection });
58+
return markup;
59+
}
60+
61+
module.exports = renderComponentToString;

0 commit comments

Comments
 (0)