Browse Source

Remove unused styles (#31)

* remove unused styles by diffing styles

* update README

* fix tests

* use a polyfill for Object.entries
add-plugins-support
Naoyuki Kanezawa 8 years ago
committed by GitHub
parent
commit
00ccd13a52
  1. 7
      README.md
  2. 1
      inject.js
  3. 7
      package.json
  4. 26
      src/babel.js
  5. 32
      src/inject.js
  6. 56
      src/render.js
  7. 47
      src/style.js
  8. 1
      style.js
  9. 9
      test/fixtures/class.out.js
  10. 9
      test/fixtures/global.out.js
  11. 9
      test/fixtures/stateless.out.js
  12. 9
      test/fixtures/whitespace.out.js

7
README.md

@ -58,22 +58,23 @@ export default () => (
The example above transpiles to the following: The example above transpiles to the following:
```js ```js
import _jsxStyleInject from 'styled-jsx/inject' import _JSXStyle from 'styled-jsx/style'
export default () => ( export default () => (
<div data-jsx='cn2o3j'> <div data-jsx='cn2o3j'>
<p data-jsx='cn2o3j'>only this paragraph will get the style :O</p> <p data-jsx='cn2o3j'>only this paragraph will get the style :O</p>
{ _jsxStyleInject('cn2o3j', `p[data-jsx=cn2o3j] {color: red;}`) } <_JSXStyle data-jsx='cn2o3j' css={`p[data-jsx=cn2o3j] {color: red;}`} />
</div> </div>
) )
``` ```
### Why It Works Like This ### Why It Works Like This
Data attributes give us style encapsulation and `_jsxStyleInject` is heavily optimized for: Data attributes give us style encapsulation and `_JSXStyle` is heavily optimized for:
- Injecting styles upon render - Injecting styles upon render
- Only injecting a certain component's style once (even if the component is included multiple times) - Only injecting a certain component's style once (even if the component is included multiple times)
- Removing unused styles
- Keeping track of styles for server-side rendering (discussed in the next section) - Keeping track of styles for server-side rendering (discussed in the next section)
### Targetting The Root ### Targetting The Root

1
inject.js

@ -1 +0,0 @@
module.exports = require('./dist/inject')

7
package.json

@ -14,7 +14,8 @@
"memory.js" "memory.js"
], ],
"dependencies": { "dependencies": {
"babel-plugin-syntax-jsx": "^6.18.0" "babel-plugin-syntax-jsx": "^6.18.0",
"object.entries": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.17.0", "ava": "^0.17.0",
@ -29,8 +30,12 @@
"fs-promise": "^1.0.0", "fs-promise": "^1.0.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-babel": "^6.1.2", "gulp-babel": "^6.1.2",
"react": "^15.4.1",
"xo": "^0.17.1" "xo": "^0.17.1"
}, },
"peerDependencies": {
"react": "^15.0"
},
"babel": { "babel": {
"presets": [ "presets": [
"es2015", "es2015",

26
src/babel.js

@ -8,7 +8,8 @@ import transform from '../lib/style-transform'
const STYLE_ATTRIBUTE = 'jsx' const STYLE_ATTRIBUTE = 'jsx'
const GLOBAL_ATTRIBUTE = 'global' const GLOBAL_ATTRIBUTE = 'global'
const MARKUP_ATTRIBUTE = 'data-jsx' const MARKUP_ATTRIBUTE = 'data-jsx'
const INJECT_METHOD = '_jsxStyleInject' const STYLE_COMPONENT = '_JSXStyle'
const STYLE_COMPONENT_CSS = 'css'
export default function ({types: t}) { export default function ({types: t}) {
const findStyles = children => ( const findStyles = children => (
@ -132,14 +133,19 @@ export default function ({types: t}) {
)) ))
path.replaceWith( path.replaceWith(
t.JSXExpressionContainer( t.JSXElement(
t.callExpression( t.JSXOpeningElement(
t.identifier(INJECT_METHOD), t.JSXIdentifier(STYLE_COMPONENT),
[ [
t.stringLiteral(id), t.JSXAttribute(
t.stringLiteral(skipTransform ? css : transform(id, css)) t.JSXIdentifier(STYLE_COMPONENT_CSS),
] t.JSXExpressionContainer(t.stringLiteral(skipTransform ? css : transform(id, css)))
) )
],
true
),
null,
[]
) )
) )
} }
@ -157,13 +163,13 @@ export default function ({types: t}) {
state.file.hasJSXStyle = false state.file.hasJSXStyle = false
}, },
exit({node, scope}, state) { exit({node, scope}, state) {
if (!(state.file.hasJSXStyle && !scope.hasBinding(INJECT_METHOD))) { if (!(state.file.hasJSXStyle && !scope.hasBinding(STYLE_COMPONENT))) {
return return
} }
const importDeclaration = t.importDeclaration( const importDeclaration = t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(INJECT_METHOD))], [t.importDefaultSpecifier(t.identifier(STYLE_COMPONENT))],
t.stringLiteral('styled-jsx/inject') t.stringLiteral('styled-jsx/style')
) )
node.body.unshift(importDeclaration) node.body.unshift(importDeclaration)

32
src/inject.js

@ -1,32 +0,0 @@
// Ours
import memory from './memory'
const isBrowser = typeof window !== 'undefined'
const tags = {}
export default function inject(id, css) {
if (isBrowser) {
// if the tag is already present we ignore it!
if (!tags[id]) {
const el = makeStyleTag(css)
tags[id] = el
memory[id] = el
}
} else {
memory[id] = css
}
}
function makeStyleTag(str) {
// based on implementation by glamor
const tag = document.createElement('style')
tag.type = 'text/css'
tag.appendChild(document.createTextNode(str))
const head = document.head || document.getElementsByTagName('head')[0]
head.appendChild(tag)
return tag
}

56
src/render.js

@ -0,0 +1,56 @@
import entries from 'object.entries'
import memory from './memory'
const {hasOwnProperty} = Object.prototype
const tags = {}
let prevStyles = {}
export default typeof window === 'undefined' ? renderOnServer : renderOnClient
function renderOnServer(components) {
for (const {props} of components) {
memory[props['data-jsx']] = props.css
}
}
function renderOnClient(components) {
const styles = {}
for (const c of components) {
styles[c.props['data-jsx']] = c
}
patch(diff(prevStyles, styles))
prevStyles = styles
}
function diff(a, b) {
const added = entries(b).filter(([k]) => !hasOwnProperty.call(a, k))
const removed = entries(a).filter(([k]) => !hasOwnProperty.call(b, k))
return [added, removed]
}
function patch([added, removed]) {
for (const [id, c] of added) {
tags[id] = makeStyleTag(c.props.css)
}
for (const [id] of removed) {
const t = tags[id]
delete tags[id]
t.parentNode.removeChild(t)
}
}
function makeStyleTag(str) {
// based on implementation by glamor
const tag = document.createElement('style')
tag.type = 'text/css'
tag.appendChild(document.createTextNode(str))
const head = document.head || document.getElementsByTagName('head')[0]
head.appendChild(tag)
return tag
}

47
src/style.js

@ -0,0 +1,47 @@
import {Component} from 'react'
import render from './render'
export default class extends Component {
componentWillMount() {
mount(this)
}
componentWillUnmount() {
unmount(this)
}
render() {
return null
}
}
const components = []
const update = typeof window === 'undefined' ? doRender : updateOnClient
let requestId
function mount(component) {
components.push(component)
update()
}
function unmount(component) {
const i = components.indexOf(component)
if (i < 0) {
return
}
components.splice(i, 1)
update()
}
function updateOnClient() {
window.cancelAnimationFrame(requestId)
requestId = window.requestAnimationFrame(() => {
requestId = null
doRender()
})
}
function doRender() {
render(components)
}

1
style.js

@ -0,0 +1 @@
module.exports = require('./dist/style')

9
test/fixtures/class.out.js

@ -12,9 +12,9 @@ var _createClass2 = require("babel-runtime/helpers/createClass");
var _createClass3 = _interopRequireDefault(_createClass2); var _createClass3 = _interopRequireDefault(_createClass2);
var _inject = require("styled-jsx/inject"); var _style = require("styled-jsx/style");
var _inject2 = _interopRequireDefault(_inject); var _style2 = _interopRequireDefault(_style);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -38,7 +38,10 @@ var _class = function () {
}, },
"test" "test"
), ),
(0, _inject2.default)("1544381438", "p[data-jsx=\"1544381438\"]{color: red;}") React.createElement(_style2.default, {
css: "p[data-jsx=\"1544381438\"]{color: red;}",
"data-jsx": "1544381438"
})
); );
} }
}]); }]);

9
test/fixtures/global.out.js

@ -1,8 +1,8 @@
"use strict"; "use strict";
var _inject = require("styled-jsx/inject"); var _style = require("styled-jsx/style");
var _inject2 = _interopRequireDefault(_inject); var _style2 = _interopRequireDefault(_style);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -11,5 +11,8 @@ React.createElement(
{ {
"data-jsx": "2938046615" "data-jsx": "2938046615"
}, },
(0, _inject2.default)("2938046615", "\n body {\n color: red\n }\n ") React.createElement(_style2.default, {
css: "\n body {\n color: red\n }\n ",
"data-jsx": "2938046615"
})
); );

9
test/fixtures/stateless.out.js

@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true value: true
}); });
var _inject = require('styled-jsx/inject'); var _style = require('styled-jsx/style');
var _inject2 = _interopRequireDefault(_inject); var _style2 = _interopRequireDefault(_style);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -37,6 +37,9 @@ exports.default = function () {
}, },
'woot' 'woot'
), ),
(0, _inject2.default)('4271158759', 'p[data-jsx="4271158759"]{color: red }') React.createElement(_style2.default, {
css: 'p[data-jsx="4271158759"]{color: red }',
'data-jsx': '4271158759'
})
); );
}; };

9
test/fixtures/whitespace.out.js

@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true value: true
}); });
var _inject = require('styled-jsx/inject'); var _style = require('styled-jsx/style');
var _inject2 = _interopRequireDefault(_inject); var _style2 = _interopRequireDefault(_style);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -37,6 +37,9 @@ exports.default = function () {
}, },
'woot' 'woot'
), ),
(0, _inject2.default)('4271158759', 'p[data-jsx="4271158759"]{color: red }') React.createElement(_style2.default, {
css: 'p[data-jsx="4271158759"]{color: red }',
'data-jsx': '4271158759'
})
); );
}; };

Loading…
Cancel
Save