Skip to content

Commit

Permalink
deterministic class names generation #221 #222 #229 #241
Browse files Browse the repository at this point in the history
  • Loading branch information
kof committed Jun 10, 2016
1 parent dababb0 commit c7c092b
Show file tree
Hide file tree
Showing 14 changed files with 53 additions and 104 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,8 @@
"posttest": "[ -z \"$TRAVIS\" ] || codecov",
"codecov": "codecov",
"bench": "BENCHMARK=true npm run test"
},
"dependencies": {
"murmurhash-js": "^1.0.0"
}
}
10 changes: 5 additions & 5 deletions src/Jss.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import hash from 'murmurhash-js/murmurhash3_gc'
import StyleSheet from './StyleSheet'
import PluginsRegistry from './PluginsRegistry'
import SheetsRegistry from './SheetsRegistry'
import {uid} from './utils'
import createRule from './createRule'
import findRenderer from './findRenderer'

Expand All @@ -11,11 +11,11 @@ import findRenderer from './findRenderer'
* @api public
*/
export default class Jss {
constructor() {
constructor(options = {}) {
this.sheets = new SheetsRegistry()
this.plugins = new PluginsRegistry()
this.uid = uid
this.version = process.env.VERSION
this.hash = options.hash || hash
}

/**
Expand All @@ -24,8 +24,8 @@ export default class Jss {
* @see Jss
* @api public
*/
create() {
return new Jss()
create(options) {
return new Jss(options)
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/StyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ export default class StyleSheet {
*/
toString(options) {
const {rules} = this
const stringified = Object.create(null)
const stringified = []
let str = ''
for (const name in rules) {
const rule = rules[name]
// We have the same rule referenced twice if using named rules.
// By name and by selector.
if (stringified[rule.id]) {
if (stringified.indexOf(rule) !== -1) {
continue
}

Expand All @@ -146,7 +146,7 @@ export default class StyleSheet {
if (str) str += '\n'

str += rule.toString(options)
stringified[rule.id] = true
stringified.push(rule)
}
return str
}
Expand Down
3 changes: 1 addition & 2 deletions src/rules/ConditionalRule.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isEmptyObject, uid} from '../utils'
import {isEmptyObject} from '../utils'

/**
* Conditional rule for @media, @supports
Expand All @@ -7,7 +7,6 @@ import {isEmptyObject, uid} from '../utils'
*/
export default class ConditionalRule {
constructor(selector, styles, options) {
this.id = uid.get()
this.type = 'conditional'
this.selector = selector
this.options = options
Expand Down
3 changes: 1 addition & 2 deletions src/rules/FontFaceRule.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {uid, toCSS} from '../utils'
import {toCSS} from '../utils'

/**
* Font-face rules.
Expand All @@ -7,7 +7,6 @@ import {uid, toCSS} from '../utils'
*/
export default class Rule {
constructor(selector, style, options) {
this.id = uid.get()
this.type = 'font-face'
this.options = options
this.selector = selector
Expand Down
3 changes: 0 additions & 3 deletions src/rules/KeyframeRule.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {uid} from '../utils'

/**
* Keyframe rule.
*
* @api private
*/
export default class KeyframeRule {
constructor(selector, frames, options) {
this.id = uid.get()
this.type = 'keyframe'
this.selector = selector
this.options = options
Expand Down
17 changes: 12 additions & 5 deletions src/rules/Rule.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {clone, uid, toCSS, findClassNames} from '../utils'
import {toCSS, findClassNames} from '../utils'
const {parse, stringify} = JSON


/**
* Regular rules.
Expand All @@ -7,18 +9,23 @@ import {clone, uid, toCSS, findClassNames} from '../utils'
*/
export default class Rule {
constructor(selector, style, options) {
this.id = uid.get()
// We expect style to be plain object.
// To avoid original style object mutations, we clone it and hash it
// along the way.
// It is also the fastetst way.
// http://jsperf.com/lodash-deepclone-vs-jquery-extend-deep/6
const styleStr = stringify(style)
const hash = options.jss.hash(styleStr)
this.style = parse(styleStr)
this.type = 'regular'
this.options = options
this.selectorText = selector || ''
this.className = options.className || ''
this.originalStyle = style
// We expect style to be plain object.
this.style = clone(style)
if (options.named) {
this.name = selector
if (!this.className) {
this.className = this.name ? `${this.name}--${this.id}` : this.id
this.className = this.name ? `${this.name}-${hash}` : hash
}
this.selectorText = `.${this.className}`
}
Expand Down
3 changes: 0 additions & 3 deletions src/rules/SimpleRule.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {uid} from '../utils'

/**
* Rule like @charset, @import, @namespace.
*
* @api public
*/
export default class SimpleRule {
constructor(name, value, options) {
this.id = uid.get()
this.type = 'simple'
this.name = name
this.value = value
Expand Down
51 changes: 0 additions & 51 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
const stringify = JSON.stringify
const parse = JSON.parse

/**
* Deeply clone object using serialization.
* Expects object to be plain and without cyclic dependencies.
*
* http://jsperf.com/lodash-deepclone-vs-jquery-extend-deep/6
*
* @type {Object} obj
* @return {Object}
*/
export function clone(obj) {
return parse(stringify(obj))
}

/**
* Determine whether an object is empty or not.
* More performant than a `Object.keys(obj).length > 0`
Expand All @@ -27,41 +11,6 @@ export function isEmptyObject(obj) {
return true
}

/**
* Simple very fast UID generation based on a global counter.
*/
export const uid = (() => {
const globalReference = typeof window == 'undefined' ? global : window
const namespace = '__JSS_VERSION_COUNTER__'
if (globalReference[namespace] == null) globalReference[namespace] = 0

// In case we have more than one jss version.
const versionCounter = globalReference[namespace]++
let ruleCounter = 0

/**
* Returns a uid.
* Ensures uniqueness if more than 1 jss version is used.
*
* @api public
* @return {String}
*/
function get() {
return `jss-${versionCounter}-${ruleCounter++}`
}

/**
* Resets the counter.
*
* @api public
*/
function reset() {
ruleCounter = 0
}

return {get, reset}
})()

/**
* Indent a string.
*
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('Functional: sheet', () => {
})

it('should render correct CSS', () => {
expect(getCss(style)).to.be('.a--jss-0-0 { float: left; }')
expect(getCss(style)).to.be('.a-id { float: left; }')
})

it('should register the rule', () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/jss.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ describe('Integration: jss', () => {

it('should return CSS of all sheets from .sheets.toString()', () => {
const css =
'.a--jss-0-0 {\n' +
'.a-id {\n' +
' color: red;\n' +
'}\n' +
'.a--jss-0-1 {\n' +
'.a-id {\n' +
' color: blue;\n' +
'}'
expect(jss.sheets.toString()).to.be(css)
Expand Down
22 changes: 9 additions & 13 deletions tests/integration/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@ afterEach(reset)
describe('Integration: rules', () => {
describe('.createRule()', () => {
it('should create a rule without args', () => {
let rule = jss.createRule()
expect(rule.type).to.be('regular')
expect(rule.className).to.be('jss-0-0')
expect(rule.selector).to.be('.jss-0-0')
rule = jss.createRule()
const rule = jss.createRule()
expect(rule.type).to.be('regular')
expect(rule.className).to.be('jss-0-1')
expect(rule.selector).to.be('.jss-0-1')
expect(rule.className).to.be('id')
expect(rule.selector).to.be('.id')
})

it('should accept styles only', () => {
const style = {float: 'left'}
const rule = jss.createRule(style)
expect(rule.style).to.eql(style)
expect(rule.type).to.be('regular')
expect(rule.className).to.be('jss-0-0')
expect(rule.selector).to.be('.jss-0-0')
expect(rule.className).to.be('id')
expect(rule.selector).to.be('.id')
})

it('should accept styles and options', () => {
Expand All @@ -32,8 +28,8 @@ describe('Integration: rules', () => {
const rule = jss.createRule(style, options)
expect(rule.style).to.eql(style)
expect(rule.type).to.be('regular')
expect(rule.className).to.be('jss-0-0')
expect(rule.selector).to.be('.jss-0-0')
expect(rule.className).to.be('id')
expect(rule.selector).to.be('.id')
expect(rule.options.named).to.be(true)
expect(rule.options.jss).to.be(jss)
expect(rule.options.something).to.be(true)
Expand Down Expand Up @@ -161,7 +157,7 @@ describe('Integration: rules', () => {
expect(rule.selector).to.be('@media print')
expect(rule.toString()).to.be(
'@media print {\n' +
' .button--jss-0-1 {\n' +
' .button-id {\n' +
' display: none;\n' +
' }\n' +
'}'
Expand Down Expand Up @@ -247,7 +243,7 @@ describe('Integration: rules', () => {
expect(rule.selector).to.be('@supports ( display: flexbox )')
const css =
'@supports ( display: flexbox ) {\n' +
' .button--jss-0-1 {\n' +
' .button-id {\n' +
' display: none;\n' +
' }\n' +
'}'
Expand Down
26 changes: 13 additions & 13 deletions tests/integration/sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ describe('Integration: sheet', () => {
const sheet = jss.createStyleSheet({a: {float: 'left'}})
const rule = sheet.getRule('a')
expect(rule).to.be.a(Rule)
expect(sheet.classes.a).to.be('a--jss-0-0')
expect(rule.className).to.be('a--jss-0-0')
expect(rule.selector).to.be('.a--jss-0-0')
expect(sheet.classes.a).to.be('a-id')
expect(rule.className).to.be('a-id')
expect(rule.selector).to.be('.a-id')
})

it('should create an unnamed sheet', () => {
Expand All @@ -38,15 +38,15 @@ describe('Integration: sheet', () => {
a: {float: 'left'}
}
})
expect(sheet.classes.a).to.be('a--jss-0-1')
expect(sheet.classes.a).to.be('a-id')
})
})

describe('sheet.getRule()', () => {
it('should return a rule by name and selector from named sheet', () => {
const sheet = jss.createStyleSheet({a: {float: 'left'}})
expect(sheet.getRule('a')).to.be.a(Rule)
expect(sheet.getRule('.a--jss-0-0')).to.be.a(Rule)
expect(sheet.getRule('.a-id')).to.be.a(Rule)
})

it('should return a rule by selector from unnamed sheet', () => {
Expand Down Expand Up @@ -123,16 +123,16 @@ describe('Integration: sheet', () => {
'@media (min-width: 1000px)': {a: {color: 'green'}}
})
expect(sheet.toString()).to.be(
'.a--jss-0-0 {\n' +
'.a-id {\n' +
' color: red;\n' +
'}\n' +
'@media (min-width: 1024px) {\n' +
' .a--jss-0-0 {\n' +
' .a-id {\n' +
' color: blue;\n' +
' }\n' +
'}\n' +
'@media (min-width: 1000px) {\n' +
' .a--jss-0-0 {\n' +
' .a-id {\n' +
' color: green;\n' +
' }\n' +
'}'
Expand All @@ -148,10 +148,10 @@ describe('Integration: sheet', () => {
d: {}
})
expect(sheet.toString()).to.be(
'.a--jss-0-0 {\n' +
'.a-id {\n' +
' color: red;\n' +
'}\n' +
'.c--jss-0-2 {\n' +
'.c-id {\n' +
' color: green;\n' +
'}'
)
Expand All @@ -165,10 +165,10 @@ describe('Integration: sheet', () => {
'@font-face': {}
})
expect(sheet.toString()).to.be(
'.a--jss-0-0 {\n' +
'.a-id {\n' +
' color: red;\n' +
'}\n' +
'.c--jss-0-2 {\n' +
'.c-id {\n' +
' color: green;\n' +
'}'
)
Expand All @@ -180,7 +180,7 @@ describe('Integration: sheet', () => {
'@media print': {}
})
expect(sheet.toString()).to.be(
'.a--jss-0-0 {\n' +
'.a-id {\n' +
' color: red;\n' +
'}'
)
Expand Down
4 changes: 3 additions & 1 deletion tests/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ export function computeStyle(className) {
export function reset() {
jss.plugins.registry = []
jss.sheets.registry = []
jss.uid.reset()
}

// Mock the hash function.
jss.hash = () => 'id'

0 comments on commit c7c092b

Please sign in to comment.