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. 28
      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:
```js
import _jsxStyleInject from 'styled-jsx/inject'
import _JSXStyle from 'styled-jsx/style'
export default () => (
<div data-jsx='cn2o3j'>
<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>
)
```
### 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
- 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)
### Targetting The Root

1
inject.js

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

7
package.json

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

28
src/babel.js

@ -8,7 +8,8 @@ import transform from '../lib/style-transform'
const STYLE_ATTRIBUTE = 'jsx'
const GLOBAL_ATTRIBUTE = 'global'
const MARKUP_ATTRIBUTE = 'data-jsx'
const INJECT_METHOD = '_jsxStyleInject'
const STYLE_COMPONENT = '_JSXStyle'
const STYLE_COMPONENT_CSS = 'css'
export default function ({types: t}) {
const findStyles = children => (
@ -132,14 +133,19 @@ export default function ({types: t}) {
))
path.replaceWith(
t.JSXExpressionContainer(
t.callExpression(
t.identifier(INJECT_METHOD),
t.JSXElement(
t.JSXOpeningElement(
t.JSXIdentifier(STYLE_COMPONENT),
[
t.stringLiteral(id),
t.stringLiteral(skipTransform ? css : transform(id, css))
]
)
t.JSXAttribute(
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
},
exit({node, scope}, state) {
if (!(state.file.hasJSXStyle && !scope.hasBinding(INJECT_METHOD))) {
if (!(state.file.hasJSXStyle && !scope.hasBinding(STYLE_COMPONENT))) {
return
}
const importDeclaration = t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(INJECT_METHOD))],
t.stringLiteral('styled-jsx/inject')
[t.importDefaultSpecifier(t.identifier(STYLE_COMPONENT))],
t.stringLiteral('styled-jsx/style')
)
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 _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 }; }
@ -38,7 +38,10 @@ var _class = function () {
},
"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";
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 }; }
@ -11,5 +11,8 @@ React.createElement(
{
"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
});
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 }; }
@ -37,6 +37,9 @@ exports.default = function () {
},
'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
});
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 }; }
@ -37,6 +37,9 @@ exports.default = function () {
},
'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