diff --git a/package.json b/package.json index 3c1c25e7e..83b1919d9 100644 --- a/package.json +++ b/package.json @@ -73,5 +73,8 @@ "posttest": "[ -z \"$TRAVIS\" ] || codecov", "codecov": "codecov", "bench": "BENCHMARK=true npm run test" + }, + "dependencies": { + "murmurhash-js": "^1.0.0" } } diff --git a/src/Jss.js b/src/Jss.js index 7486cbadb..da01167e7 100644 --- a/src/Jss.js +++ b/src/Jss.js @@ -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' @@ -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 } /** @@ -24,8 +24,8 @@ export default class Jss { * @see Jss * @api public */ - create() { - return new Jss() + create(options) { + return new Jss(options) } /** diff --git a/src/StyleSheet.js b/src/StyleSheet.js index 637823676..c196bc1d4 100644 --- a/src/StyleSheet.js +++ b/src/StyleSheet.js @@ -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 } @@ -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 } diff --git a/src/rules/ConditionalRule.js b/src/rules/ConditionalRule.js index 34b53da65..4ca3c7033 100644 --- a/src/rules/ConditionalRule.js +++ b/src/rules/ConditionalRule.js @@ -1,4 +1,4 @@ -import {isEmptyObject, uid} from '../utils' +import {isEmptyObject} from '../utils' /** * Conditional rule for @media, @supports @@ -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 diff --git a/src/rules/FontFaceRule.js b/src/rules/FontFaceRule.js index 07ae7b59c..c3f25485d 100644 --- a/src/rules/FontFaceRule.js +++ b/src/rules/FontFaceRule.js @@ -1,4 +1,4 @@ -import {uid, toCSS} from '../utils' +import {toCSS} from '../utils' /** * Font-face rules. @@ -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 diff --git a/src/rules/KeyframeRule.js b/src/rules/KeyframeRule.js index 10d07ae85..4d5540ff1 100644 --- a/src/rules/KeyframeRule.js +++ b/src/rules/KeyframeRule.js @@ -1,5 +1,3 @@ -import {uid} from '../utils' - /** * Keyframe rule. * @@ -7,7 +5,6 @@ import {uid} from '../utils' */ export default class KeyframeRule { constructor(selector, frames, options) { - this.id = uid.get() this.type = 'keyframe' this.selector = selector this.options = options diff --git a/src/rules/Rule.js b/src/rules/Rule.js index 905719a0c..5480b3013 100644 --- a/src/rules/Rule.js +++ b/src/rules/Rule.js @@ -1,4 +1,6 @@ -import {clone, uid, toCSS, findClassNames} from '../utils' +import {toCSS, findClassNames} from '../utils' +const {parse, stringify} = JSON + /** * Regular rules. @@ -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}` } diff --git a/src/rules/SimpleRule.js b/src/rules/SimpleRule.js index 6fed7629d..2dfc3a692 100644 --- a/src/rules/SimpleRule.js +++ b/src/rules/SimpleRule.js @@ -1,5 +1,3 @@ -import {uid} from '../utils' - /** * Rule like @charset, @import, @namespace. * @@ -7,7 +5,6 @@ import {uid} from '../utils' */ export default class SimpleRule { constructor(name, value, options) { - this.id = uid.get() this.type = 'simple' this.name = name this.value = value diff --git a/src/utils.js b/src/utils.js index 901853f64..451b68af6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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` @@ -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. * diff --git a/tests/functional/sheet.js b/tests/functional/sheet.js index 17c03c027..16bdc7446 100644 --- a/tests/functional/sheet.js +++ b/tests/functional/sheet.js @@ -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', () => { diff --git a/tests/integration/jss.js b/tests/integration/jss.js index 5f9ebcda6..8d3fdaf6d 100644 --- a/tests/integration/jss.js +++ b/tests/integration/jss.js @@ -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) diff --git a/tests/integration/rules.js b/tests/integration/rules.js index d8381874b..5527d0407 100644 --- a/tests/integration/rules.js +++ b/tests/integration/rules.js @@ -7,14 +7,10 @@ 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', () => { @@ -22,8 +18,8 @@ describe('Integration: rules', () => { 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', () => { @@ -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) @@ -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' + '}' @@ -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' + '}' diff --git a/tests/integration/sheet.js b/tests/integration/sheet.js index 5da0ac13b..a312bbf78 100755 --- a/tests/integration/sheet.js +++ b/tests/integration/sheet.js @@ -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', () => { @@ -38,7 +38,7 @@ describe('Integration: sheet', () => { a: {float: 'left'} } }) - expect(sheet.classes.a).to.be('a--jss-0-1') + expect(sheet.classes.a).to.be('a-id') }) }) @@ -46,7 +46,7 @@ describe('Integration: sheet', () => { 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', () => { @@ -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' + '}' @@ -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' + '}' ) @@ -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' + '}' ) @@ -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' + '}' ) diff --git a/tests/utils.js b/tests/utils.js index 98f045d5d..3c453ff2c 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -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'