From 8f0e6cbc85f3c1f6bf988004b6d1824ff763f199 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sun, 24 Apr 2022 13:46:35 +0800 Subject: [PATCH 1/2] refactor: use djb2 hashing --- package.json | 1 + src/Prefixer.js | 76 ++++++++++++++++++++++++++---------------------- src/Utility.js | 10 +++++-- test/Prefixer.js | 16 +++++----- test/Utility.js | 15 ++++++++++ 5 files changed, 73 insertions(+), 45 deletions(-) create mode 100644 test/Utility.js diff --git a/package.json b/package.json index 1860c0b..5ea26e9 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "chai": "4.3.4", "eslint": "6.8.0", "esm": "3.2.25", + "known-css-properties": "^0.24.0", "mocha": "9.1.1", "nyc": "15.1.0", "rimraf": "3.0.2", diff --git a/src/Prefixer.js b/src/Prefixer.js index 5e9586e..2a24b4f 100644 --- a/src/Prefixer.js +++ b/src/Prefixer.js @@ -10,25 +10,31 @@ import {hash, charat, strlen, indexof, replace, substr, match} from './Utility.j export function prefix (value, length, children) { switch (hash(value, length)) { // color-adjust - case 5103: + case 4147921980: return WEBKIT + 'print-' + value + value - // animation, animation-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function) - case 5737: case 4201: case 3177: case 3433: case 1641: case 4457: case 2921: + // animation, ançimation-(delay|direction|duration|fill-mode) + case 831438501: case 6084974529: case 8960475411: case 9778076536: case 5329955115: + // animation-(iteration-count|name|play-state|timing-function) + case 13246805623: case 4609866035: case 5300880246: case 4168473101: // text-decoration, filter, clip-path, backface-visibility, column, box-decoration-break - case 5572: case 6356: case 5844: case 3191: case 6645: case 3005: - // mask, mask-image, mask-(mode|clip|size), mask-(repeat|origin), mask-position, mask-composite, - case 6391: case 5879: case 5623: case 6135: case 4599: case 4855: - // background-clip, columns, column-(count|fill|gap|rule|rule-color|rule-style|rule-width|span|width) - case 4215: case 6389: case 5109: case 5365: case 5621: case 3829: + case 573572639: case -42568277: case -3131825241: case -3300488806: case -152857581: case -2974525739: + // mask, mask-image, mask-(mode|clip|size) + case 2090500273: case -803200447: case -1585999677: case -1586362138: case -1585789863: + // mask-(repeat|origin), mask-position, mask-composite, background-clip + case -392542945: case -494781274: case -1462442573: case 457275345: case -1500969926: + // columns, column-(count|fill|gap|rule|rule-color) + case -749332762: case 655069929: case 670704199: case 671077816: case 671148504: case 1285898820: + // columns-(rule-style|rule-width|span|width) + case 1305067286: case 1309393061: case 671178642: case 678554400: return WEBKIT + value + value // tab-size - case 4789: + case -1671331900: return MOZ + value + value // appearance, user-select, transform, hyphens, text-size-adjust - case 5349: case 4246: case 4810: case 6968: case 2756: + case 5808179221: case -2322612495: case 596893057: case 1808661508: case -992853366: return WEBKIT + value + MOZ + value + MS + value + value // writing-mode - case 5936: + case -604349445: switch (charat(value, length + 11)) { // vertical-l(r) case 114: @@ -42,65 +48,65 @@ export function prefix (value, length, children) { // default: fallthrough to below } // flex, flex-direction, scroll-snap-type, writing-mode - case 6828: case 4268: case 2903: + case 2090260244: case 1330556994: case -4992262046: return WEBKIT + value + MS + value + value // order - case 6165: + case 269998625: return WEBKIT + value + MS + 'flex-' + value + value // align-items - case 5187: + case 2175947679: return WEBKIT + value + replace(value, /(\w+).+(:[^]+)/, WEBKIT + 'box-$1$2' + MS + 'flex-$1$2') + value // align-self - case 5443: + case 3320044167: return WEBKIT + value + MS + 'flex-item-' + replace(value, /flex-|-self/g, '') + (!match(value, /flex-|baseline/) ? MS + 'grid-row-' + replace(value, /flex-|-self/g, '') : '') + value // align-content - case 4675: + case 3736405496: return WEBKIT + value + MS + 'flex-line-pack' + replace(value, /align-content|flex-|-self/g, '') + value // flex-shrink - case 5548: + case 4607167408: return WEBKIT + value + MS + replace(value, 'shrink', 'negative') + value // flex-basis - case 5292: + case 2982511539: return WEBKIT + value + MS + replace(value, 'basis', 'preferred-size') + value // flex-grow - case 6060: + case 2172985600: return WEBKIT + 'box-' + replace(value, '-grow', '') + WEBKIT + value + MS + replace(value, 'grow', 'positive') + value // transition - case 4554: + case 2521329520: return WEBKIT + replace(value, /([^-])(transform)/g, '$1' + WEBKIT + '$2') + value // cursor - case 6187: + case -145528541: return replace(replace(replace(value, /(zoom-|grab)/, WEBKIT + '$1'), /(image-set)/, WEBKIT + '$1'), value, '') + value // background, background-image - case 5495: case 3959: + case -2558650491: case -2280224747: return replace(value, /(image-set\([^]*)/, WEBKIT + '$1' + '$`$1') // justify-content - case 4968: + case 3563359163: return replace(replace(value, /(.+:)(flex-)?(.*)/, WEBKIT + 'box-pack:$3' + MS + 'flex-pack:$3'), /s.+-b[^;]+/, 'justify') + WEBKIT + value + value // justify-self - case 4200: + case -632520918: if (!match(value, /flex-|baseline/)) return MS + 'grid-column-align' + substr(value, length) + value break // grid-template-(columns|rows) - case 2592: case 3360: + case -6598323198: case -8311231156: return MS + replace(value, 'template-', '') + value // grid-(row|column)-start - case 4384: case 3616: + case -1491776085: case -9173721407: if (children && children.some(function (element, index) { return length = index, match(element.props, /grid-\w+-end/) })) { return ~indexof(value + (children = children[length].value), 'span') ? value : (MS + replace(value, '-start', '') + value + MS + 'grid-row-span:' + (~indexof(children, 'span') ? match(children, /\d+/) : +match(children, /\d+/) - +match(value, /\d+/)) + ';') } else { return MS + replace(value, '-start', '') + value } // grid-(row|column)-end - case 4896: case 4128: + case -2395366156: case -9694793590: return (children && children.some(function (element) { return match(element.props, /grid-\w+-start/) })) ? value : MS + replace(replace(value, '-end', '-span'), 'span ', '') + value // (margin|padding)-inline-(start|end) - case 4095: case 3583: case 4068: case 2532: + case -6031848726: case -3511730573: case 2114080355: case 1090457516: return replace(value, /(.+)-inline(.+)/, WEBKIT + '$1$2') + value // (min|max)?(width|height|inline-size|block-size) - case 8116: case 7059: case 5753: case 5535: - case 5445: case 5701: case 4933: case 4677: - case 5533: case 5789: case 5021: case 4765: + case 279163045: case 30836958: case 1762183148: case -3882807592: + case -147068906: case -1149915537: case -1625092067: case -3464850199: + case -1762418536: case -2916845775: case -4851260897: case -5905322325: // stretch, max-content, min-content, fill-available if (strlen(value) - 1 - length > 6) switch (charat(value, length + 1)) { @@ -118,16 +124,16 @@ export function prefix (value, length, children) { } break // grid-(column|row) - case 5152: case 5920: + case -4694600634: case -843523152: return replace(value, /(.+?):(\d+)(\s*\/\s*(span)?\s*(\d+))?(.*)/, function (_, a, b, c, d, e, f) { return (MS + a + ':' + b + f) + (c ? (MS + a + '-span:' + (d ? e : +e - +b)) + f : '') + value }) // position: sticky - case 4949: + case -3004204358: // stick(y)? if (charat(value, length + 6) === 121) return replace(value, ':', ':' + WEBKIT) + value break // display: (flex|inline-flex|grid|inline-grid) - case 6444: + case 315443099: switch (charat(value, charat(value, 14) === 45 ? 18 : 11)) { // (inline-)?fle(x) case 120: @@ -138,7 +144,7 @@ export function prefix (value, length, children) { } break // scroll-margin, scroll-margin-(top|right|bottom|left) - case 5719: case 2647: case 2135: case 3927: case 2391: + case -6370664225: case -7904861793: case -5577592406: case -4290493535: case -7457667305: return replace(value, 'scroll-', 'scroll-snap-') + value } diff --git a/src/Utility.js b/src/Utility.js index a27883b..765348b 100644 --- a/src/Utility.js +++ b/src/Utility.js @@ -16,13 +16,19 @@ export var from = String.fromCharCode */ export var assign = Object.assign -/** + +/** A djb2 hash implementation + * * @param {string} value * @param {number} length * @return {number} */ export function hash (value, length) { - return (((((((length << 2) ^ charat(value, 0)) << 2) ^ charat(value, 1)) << 2) ^ charat(value, 2)) << 2) ^ charat(value, 3) + var h = 5381 + for (var i = 0; i < length; i++) { + h = ((h << 5) + h) + charat(value, i) + } + return h } /** diff --git a/test/Prefixer.js b/test/Prefixer.js index 23ea154..3e67653 100644 --- a/test/Prefixer.js +++ b/test/Prefixer.js @@ -90,16 +90,16 @@ describe('Prefixer', () => { }) test('mask', () => { - expect(prefix(`mask:none;`, 10)).to.equal([`-webkit-mask:none;`, `mask:none;`].join('')) + expect(prefix(`mask:none;`, 4)).to.equal([`-webkit-mask:none;`, `mask:none;`].join('')) expect(prefix(`mask-image:none;`, 10)).to.equal([`-webkit-mask-image:none;`, `mask-image:none;`].join('')) expect(prefix(`mask-image:linear-gradient(#fff);`, 10)).to.equal([`-webkit-mask-image:linear-gradient(#fff);`, `mask-image:linear-gradient(#fff);`].join('')) - expect(prefix(`mask-mode:none;`, 10)).to.equal([`-webkit-mask-mode:none;`, `mask-mode:none;`].join('')) - expect(prefix(`mask-clip:none;`, 10)).to.equal([`-webkit-mask-clip:none;`, `mask-clip:none;`].join('')) - expect(prefix(`mask-size:none;`, 10)).to.equal([`-webkit-mask-size:none;`, `mask-size:none;`].join('')) - expect(prefix(`mask-repeat:none;`, 10)).to.equal([`-webkit-mask-repeat:none;`, `mask-repeat:none;`].join('')) - expect(prefix(`mask-origin:none;`, 10)).to.equal([`-webkit-mask-origin:none;`, `mask-origin:none;`].join('')) - expect(prefix(`mask-position:none;`, 10)).to.equal([`-webkit-mask-position:none;`, `mask-position:none;`].join('')) - expect(prefix(`mask-composite:none;`, 10)).to.equal([`-webkit-mask-composite:none;`, `mask-composite:none;`].join('')) + expect(prefix(`mask-mode:none;`, 9)).to.equal([`-webkit-mask-mode:none;`, `mask-mode:none;`].join('')) + expect(prefix(`mask-clip:none;`, 9)).to.equal([`-webkit-mask-clip:none;`, `mask-clip:none;`].join('')) + expect(prefix(`mask-size:none;`, 9)).to.equal([`-webkit-mask-size:none;`, `mask-size:none;`].join('')) + expect(prefix(`mask-repeat:none;`, 11)).to.equal([`-webkit-mask-repeat:none;`, `mask-repeat:none;`].join('')) + expect(prefix(`mask-origin:none;`, 11)).to.equal([`-webkit-mask-origin:none;`, `mask-origin:none;`].join('')) + expect(prefix(`mask-position:none;`, 13)).to.equal([`-webkit-mask-position:none;`, `mask-position:none;`].join('')) + expect(prefix(`mask-composite:none;`, 14)).to.equal([`-webkit-mask-composite:none;`, `mask-composite:none;`].join('')) }) test('filter', () => { diff --git a/test/Utility.js b/test/Utility.js new file mode 100644 index 0000000..2f61b07 --- /dev/null +++ b/test/Utility.js @@ -0,0 +1,15 @@ +import {all} from 'known-css-properties' +import {hash} from '../index.js' + +describe('Utility', () => { + test('hash - no collision', () => { + const hashMap = all.reduce((map, property) => { + const key = hash(property, property.length) + map[key] = map[key] || [] + map[key].push(property) + return map + }, {}) + + expect(Object.fromEntries(Object.entries(hashMap).filter(([, value]) => value.length > 1))).to.deep.equal({}) + }) +}) From d568d5628d26c5b662fac1446cdc8163029a1295 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sun, 24 Apr 2022 14:08:38 +0800 Subject: [PATCH 2/2] test: do not use Object.fromEntries --- test/Utility.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Utility.js b/test/Utility.js index 2f61b07..f970642 100644 --- a/test/Utility.js +++ b/test/Utility.js @@ -10,6 +10,9 @@ describe('Utility', () => { return map }, {}) - expect(Object.fromEntries(Object.entries(hashMap).filter(([, value]) => value.length > 1))).to.deep.equal({}) + expect((Object.entries(hashMap).filter(([, value]) => value.length > 1)).reduce((obj, [key, val]) => { + obj[key] = val + return obj + }, {})).to.deep.equal({}) }) })