diff --git a/packages/postcss-reduce-idents/src/__tests__/index.js b/packages/postcss-reduce-idents/src/__tests__/index.js index 8eb723caa..c5f76ac84 100644 --- a/packages/postcss-reduce-idents/src/__tests__/index.js +++ b/packages/postcss-reduce-idents/src/__tests__/index.js @@ -13,6 +13,13 @@ test( '@keyframes a{0%{color:#fff}to{color:#000}}.one{animation-name:a}' ); +test( + 'should rename keyframes (uppercase)', + processCSS, + '@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}.one{ANIMATION-NAME:whiteToBlack}', + '@KEYFRAMES a{0%{color:#fff}to{color:#000}}.one{ANIMATION-NAME:a}' +); + test( 'should rename multiple keyframes', processCSS, @@ -20,6 +27,13 @@ test( '@keyframes a{0%{color:#fff}to{color:#000}}@keyframes b{0%{opacity:1}to{opacity:0}}.one{animation-name:a}.two{animation-name:b}', ); +test( + 'should rename multiple keyframes (uppercase)', + processCSS, + '@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}@KEYFRAMES fadeOut{0%{opacity:1}to{opacity:0}}.one{animation-name:whiteToBlack}.two{animation-name:fadeOut}', + '@KEYFRAMES a{0%{color:#fff}to{color:#000}}@KEYFRAMES b{0%{opacity:1}to{opacity:0}}.one{animation-name:a}.two{animation-name:b}', +); + test( 'should reuse the same animation name for vendor prefixed keyframes', processCSS, @@ -27,6 +41,13 @@ test( '@-webkit-keyframes a{0%{color:#fff}to{color:#000}}@keyframes a{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:a;animation-name:a}' ); +test( + 'should reuse the same animation name for vendor prefixed keyframes', + processCSS, + '@-WEBKIT-KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:whiteToBlack;animation-name:whiteToBlack}', + '@-WEBKIT-KEYFRAMES a{0%{color:#fff}to{color:#000}}@KEYFRAMES a{0%{color:#fff}to{color:#000}}div{-webkit-animation-name:a;animation-name:a}' +); + test( 'should support multiple animations', processCSS, @@ -34,12 +55,25 @@ test( '@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes b{0%{border-width:0;opacity:0}}.loader{animation:a 1250ms infinite linear, b .3s ease-out both}' ); +test( + 'should support multiple animations (uppercase)', + processCSS, + '@KEYFRAMES one{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@KEYFRAMES two{0%{border-width:0;opacity:0}}.loader{animation:one 1250ms infinite linear, two .3s ease-out both}', + '@KEYFRAMES a{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@KEYFRAMES b{0%{border-width:0;opacity:0}}.loader{animation:a 1250ms infinite linear, b .3s ease-out both}' +); + test( 'should not touch animation names that are not defined in the file', passthroughCSS, '.one{animation-name:fadeInUp}' ); +test( + 'should not touch animation names that are not defined in the file (uppercase)', + passthroughCSS, + '.one{ANIMATION-NAME:fadeInUp}' +); + test( 'should not touch keyframes that are not referenced in the file', passthroughCSS, @@ -59,6 +93,13 @@ test( '@counter-style a{system:extends decimal;suffix:"> "}ol{list-style:a}' ); +test( + 'should rename counter styles (uppercase)', + processCSS, + '@COUNTER-STYLE custom{system:extends decimal;suffix:"> "}ol{LIST-STYLE:custom}', + '@COUNTER-STYLE a{system:extends decimal;suffix:"> "}ol{LIST-STYLE:a}' +); + test( 'should rename multiple counter styles & be aware of extensions', processCSS, @@ -85,6 +126,13 @@ test( 'body{counter-reset:a}h3:before{counter-increment:a;content:"Section" counter(a) ": "}' ); +test( + 'should rename counters (uppercase)', + processCSS, + 'body{COUNTER-RESET:section}h3:before{COUNTER-INCREMENT:section;CONTENT:"Section" counter(section) ": "}', + 'body{COUNTER-RESET:a}h3:before{COUNTER-INCREMENT:a;CONTENT:"Section" counter(a) ": "}' +); + test( 'should rename counters (2)', processCSS, @@ -99,6 +147,13 @@ test( 'li{counter-increment:a}li::marker{content:"(" counters(a,".") ")"}' ); +test( + 'should rename counters (3) (uppercase)', + processCSS, + 'li{counter-increment:list-item}li::marker{content:"(" COUNTERS(list-item,".") ")"}', + 'li{counter-increment:a}li::marker{content:"(" COUNTERS(a,".") ")"}' +); + test( 'should rename multiple counters', processCSS, @@ -192,6 +247,25 @@ test( ].join('') ); +test( + 'should rename grid-template-areas and grid-area (uppercase)', + processCSS, + [ + 'body{GRID-TEMPLATE-AREAS:"head head" \n"nav main"\n"nav foot";}', + 'header { GRID-AREA: head }', + 'nav{GRID-AREA:nav}', + 'main{GRID-AREA:main}', + 'footer{GRID-AREA:foot}', + ].join(''), + [ + 'body{GRID-TEMPLATE-AREAS:"a a" "b c" "b d";}', + 'header { GRID-AREA: a }', + 'nav{GRID-AREA:b}', + 'main{GRID-AREA:c}', + 'footer{GRID-AREA:d}', + ].join('') +); + test( 'should rename grid-template short syntax', processCSS, @@ -254,10 +328,14 @@ test('should not generate same ident when plugin instance is reused', t => { const instance = postcss(plugin); return Promise.all([ instance.process('@keyframes whiteToBlack{0%{color:#fff}to{color:#000}}.one{animation-name:whiteToBlack}'), + instance.process('@KEYFRAMES whiteToBlack{0%{color:#fff}to{color:#000}}.one{animation-name:whiteToBlack}'), instance.process('@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.two{animation-name:fadeOut}'), - ]).then(([result1, result2]) => { + instance.process('@KEYFRAMES fadeOut{0%{opacity:1}to{opacity:0}}.two{animation-name:fadeOut}'), + ]).then(([result1, result2, result3, result4]) => { t.is(result1.css, '@keyframes a{0%{color:#fff}to{color:#000}}.one{animation-name:a}'); - t.is(result2.css, '@keyframes b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}'); + t.is(result2.css, '@KEYFRAMES a{0%{color:#fff}to{color:#000}}.one{animation-name:a}'); + t.is(result3.css, '@keyframes b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}'); + t.is(result4.css, '@KEYFRAMES b{0%{opacity:1}to{opacity:0}}.two{animation-name:b}'); }); }); diff --git a/packages/postcss-reduce-idents/src/index.js b/packages/postcss-reduce-idents/src/index.js index f8eefc51c..10692c7ec 100644 --- a/packages/postcss-reduce-idents/src/index.js +++ b/packages/postcss-reduce-idents/src/index.js @@ -14,6 +14,7 @@ export default postcss.plugin('postcss-reduce-idents', ({ encoder = encode, } = {}) => { const reducers = []; + counter && reducers.push(counterReducer()); counterStyle && reducers.push(counterStyleReducer()); keyframes && reducers.push(keyframesReducer()); diff --git a/packages/postcss-reduce-idents/src/lib/cache.js b/packages/postcss-reduce-idents/src/lib/cache.js index 883c07a6c..593c24202 100644 --- a/packages/postcss-reduce-idents/src/lib/cache.js +++ b/packages/postcss-reduce-idents/src/lib/cache.js @@ -2,6 +2,7 @@ export default function (value, encoder, cache) { if (cache[value]) { return; } + cache[value] = { ident: encoder(value, Object.keys(cache).length), count: 0, diff --git a/packages/postcss-reduce-idents/src/lib/counter-style.js b/packages/postcss-reduce-idents/src/lib/counter-style.js index 3c9aedb32..b1ba22f88 100644 --- a/packages/postcss-reduce-idents/src/lib/counter-style.js +++ b/packages/postcss-reduce-idents/src/lib/counter-style.js @@ -25,14 +25,15 @@ export default function () { if ( type === 'atrule' && - /counter-style/.test(name) && - RESERVED_KEYWORDS.indexOf(node.params) === -1 + /counter-style/i.test(name) && + RESERVED_KEYWORDS.indexOf(node.params.toLowerCase()) === -1 ) { addToCache(node.params, encoder, cache); + atRules.push(node); } - if (type === 'decl' && /(list-style|system)/.test(prop)) { + if (type === 'decl' && /(list-style|system)/i.test(prop)) { decls.push(node); } }, @@ -43,13 +44,16 @@ export default function () { decl.value = valueParser(decl.value).walk(node => { if (node.type === 'word' && node.value in cache) { cache[node.value].count++; + node.value = cache[node.value].ident; } }).toString(); }); + // Iterate each at rule and change their name if references to them have been found atRules.forEach(rule => { const cached = cache[rule.params]; + if (cached && cached.count > 0) { rule.params = cached.ident; } diff --git a/packages/postcss-reduce-idents/src/lib/counter.js b/packages/postcss-reduce-idents/src/lib/counter.js index fc47cf508..cae4535d3 100644 --- a/packages/postcss-reduce-idents/src/lib/counter.js +++ b/packages/postcss-reduce-idents/src/lib/counter.js @@ -19,19 +19,21 @@ export default function () { return; } - if (/counter-(reset|increment)/.test(prop)) { + if (/counter-(reset|increment)/i.test(prop)) { node.value = valueParser(node.value).walk(child => { if ( child.type === 'word' && !isNum(child) && - RESERVED_KEYWORDS.indexOf(child.value) === -1 + RESERVED_KEYWORDS.indexOf(child.value.toLowerCase()) === -1 ) { addToCache(child.value, encoder, cache); + child.value = cache[child.value].ident; } }); + declOneCache.push(node); - } else if (/content/.test(prop)) { + } else if (/content/i.test(prop)) { declTwoCache.push(node); } }, @@ -39,26 +41,34 @@ export default function () { transform () { declTwoCache.forEach(decl => { decl.value = valueParser(decl.value).walk(node => { - const {type, value} = node; + const {type} = node; + + const value = node.value.toLowerCase(); + if (type === 'function' && (value === 'counter' || value === 'counters')) { walk(node.nodes, child => { if (child.type === 'word' && child.value in cache) { cache[child.value].count++; + child.value = cache[child.value].ident; } }); } + if (type === 'space') { node.value = ' '; } + return false; }).toString(); }); + declOneCache.forEach(decl => { decl.value = decl.value.walk(node => { if (node.type === 'word' && !isNum(node)) { Object.keys(cache).forEach(key => { const cached = cache[key]; + if (cached.ident === node.value && !cached.count) { node.value = key; } diff --git a/packages/postcss-reduce-idents/src/lib/encode.js b/packages/postcss-reduce-idents/src/lib/encode.js index 1b24d83b7..95d1d20c9 100644 --- a/packages/postcss-reduce-idents/src/lib/encode.js +++ b/packages/postcss-reduce-idents/src/lib/encode.js @@ -4,6 +4,7 @@ export default function encode (val, num) { let character = num % base; let result = characters[character]; let remainder = Math.floor(num / base); + if (remainder) { base = 64; characters = characters + '0123456789-_'; @@ -13,5 +14,6 @@ export default function encode (val, num) { result = result + characters[character]; } } + return result; }; diff --git a/packages/postcss-reduce-idents/src/lib/grid-template.js b/packages/postcss-reduce-idents/src/lib/grid-template.js index 3e3ff4d94..a00217879 100644 --- a/packages/postcss-reduce-idents/src/lib/grid-template.js +++ b/packages/postcss-reduce-idents/src/lib/grid-template.js @@ -16,25 +16,27 @@ export default function () { return; } - if (/(grid-template|grid-template-areas)/.test(node.prop)) { + if (/(grid-template|grid-template-areas)/i.test(node.prop)) { valueParser(node.value).walk(child => { if (child.type === 'string') { child.value.split(/\s+/).forEach(word => { if (/\.+/.test(word)) { // reduce empty zones to a single `.` node.value = node.value.replace(word, "."); - } else if (word && RESERVED_KEYWORDS.indexOf(word) === -1) { + } else if (word && RESERVED_KEYWORDS.indexOf(word.toLowerCase()) === -1) { addToCache(word, encoder, cache); } }); } }); + declCache.push(node); - } else if (node.prop === 'grid-area') { + } else if (node.prop.toLowerCase() === 'grid-area') { valueParser(node.value).walk(child => { if (child.type === 'word' && RESERVED_KEYWORDS.indexOf(child.value) === -1) { addToCache(child.value, encoder, cache); } }); + declCache.push(node); } }, @@ -42,7 +44,7 @@ export default function () { transform () { declCache.forEach(decl => { decl.value = valueParser(decl.value).walk(node => { - if (/(grid-template|grid-template-areas)/.test(decl.prop)) { + if (/(grid-template|grid-template-areas)/i.test(decl.prop)) { node.value.split(/\s+/).forEach(word => { if (word in cache) { node.value = node.value.replace(word, cache[word].ident); @@ -50,11 +52,13 @@ export default function () { }); node.value = node.value.replace(/\s+/g, " "); // merge white-spaces } - if (decl.prop === 'grid-area' && !isNum(node)) { + + if (decl.prop.toLowerCase() === 'grid-area' && !isNum(node)) { if (node.value in cache) { node.value = cache[node.value].ident; } } + return false; }).toString(); }); diff --git a/packages/postcss-reduce-idents/src/lib/keyframes.js b/packages/postcss-reduce-idents/src/lib/keyframes.js index 7cca09ba4..89d5bca6a 100644 --- a/packages/postcss-reduce-idents/src/lib/keyframes.js +++ b/packages/postcss-reduce-idents/src/lib/keyframes.js @@ -16,20 +16,21 @@ export default function () { if ( type === 'atrule' && - /keyframes/.test(name) && - RESERVED_KEYWORDS.indexOf(node.params) === -1 + /keyframes/i.test(name) && + RESERVED_KEYWORDS.indexOf(node.params.toLowerCase()) === -1 ) { addToCache(node.params, encoder, cache); atRules.push(node); } - if (type === 'decl' && /animation/.test(prop)) { + if (type === 'decl' && /animation/i.test(prop)) { decls.push(node); } }, transform () { let referenced = []; + // Iterate each property and change their names decls.forEach(decl => { decl.value = valueParser(decl.value).walk(node => { @@ -37,6 +38,7 @@ export default function () { if (!~referenced.indexOf(node.value)) { referenced.push(node.value); } + cache[node.value].count++; node.value = cache[node.value].ident; } @@ -46,6 +48,7 @@ export default function () { // Iterate each at rule and change their name if references to them have been found atRules.forEach(rule => { const cached = cache[rule.params]; + if (cached && cached.count > 0 && !!~referenced.indexOf(rule.params)) { rule.params = cached.ident; }