diff --git a/README.md b/README.md index 40f2dc0..c2cc8b2 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Default: viewportWidth: 320, viewportHeight: 568, // not now used; TODO: need for different units and math for different properties unitPrecision: 5, + propList: ['*'], viewportUnit: 'vw', fontViewportUnit: 'vw', // vmin is more suitable. selectorBlackList: [], @@ -108,7 +109,13 @@ Default: - `unitToConvert` (String) unit to convert, by default, it is px. - `viewportWidth` (Number) The width of the viewport. - `viewportHeight` (Number) The height of the viewport. -- `unitPrecision` (Number) The decimal numbers to allow the REM units to grow to. +- `unitPrecision` (Number) The decimal numbers to allow the vw units to grow to. +- `propList` (Array) The properties that can change from px to vw. + - Values need to be exact matches. + - Use wildcard * to enable all properties. Example: ['*'] + - Use * at the start or end of a word. (['*position*'] will match background-position-y) + - Use ! to not match a property. Example: ['*', '!letter-spacing'] + - Combine the "not" prefix with the other prefixes. Example: ['*', '!font*'] - `viewportUnit` (String) Expected units. - `fontViewportUnit` (String) Expected units for font. - `selectorBlackList` (Array) The selectors to ignore and leave as px. diff --git a/index.js b/index.js index 9dfb5f0..7e3be9f 100755 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ var postcss = require('postcss'); var objectAssign = require('object-assign'); +var { createPropListMatcher } = require('./src/prop-list-matcher'); var defaults = { unitToConvert: 'px', @@ -11,13 +12,14 @@ var defaults = { viewportUnit: 'vw', fontViewportUnit: 'vw', // vmin is more suitable. selectorBlackList: [], + propList: ['*'], minPixelValue: 1, mediaQuery: false, replace: true }; module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { - + var opts = objectAssign({}, defaults, options); var pxReplace = createPxReplace(opts.viewportWidth, opts.minPixelValue, opts.unitPrecision, opts.viewportUnit); @@ -27,7 +29,8 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { // Not anything inside url() // Any digit followed by px // !singlequotes|!doublequotes|!url()|pixelunit - var pxRegex = new RegExp('"[^"]+"|\'[^\']+\'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)' + opts.unitToConvert, 'g') + var pxRegex = new RegExp('"[^"]+"|\'[^\']+\'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)' + opts.unitToConvert, 'g'); + var satisfyPropList = createPropListMatcher(opts.propList); return function (css) { @@ -45,11 +48,12 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { throw new Error('options.exclude should be RegExp or Array!'); } } - - // This should be the fastest test and will remove most declarations - if (decl.value.indexOf(opts.unitToConvert) === -1) return; - if (blacklistedSelector(opts.selectorBlackList, decl.parent.selector)) return; + if ( + decl.value.indexOf(opts.unitToConvert) === -1 || + !satisfyPropList(decl.prop) || + blacklistedSelector(opts.selectorBlackList, decl.parent.selector) + ) return; var unit = getUnit(decl.prop, opts); var value = decl.value.replace(pxRegex, createPxReplace(opts.viewportWidth, opts.minPixelValue, opts.unitPrecision, unit)); diff --git a/spec/px-to-viewport.spec.js b/spec/px-to-viewport.spec.js index d530061..3f3fa54 100644 --- a/spec/px-to-viewport.spec.js +++ b/spec/px-to-viewport.spec.js @@ -9,6 +9,7 @@ var postcss = require('postcss'); var pxToViewport = require('..'); var basicCSS = '.rule { font-size: 15px }'; +var { filterPropList } = require('../src/prop-list-matcher'); describe('px-to-viewport', function() { it('should work on the readme example', function () { @@ -205,6 +206,38 @@ describe('mediaQuery', function () { }); }); +describe('propList', function () { + it('should only replace properties in the prop list', function () { + var css = '.rule { font-size: 16px; margin: 16px; margin-left: 5px; padding: 5px; padding-right: 16px }'; + var expected = '.rule { font-size: 5vw; margin: 5vw; margin-left: 5px; padding: 5px; padding-right: 5vw }'; + var options = { + propList: ['*font*', 'margin*', '!margin-left', '*-right', 'pad'] + }; + var processed = postcss(pxToViewport(options)).process(css).css; + + expect(processed).toBe(expected); + }); + + it('should only replace properties in the prop list with wildcard', function () { + var css = '.rule { font-size: 16px; margin: 16px; margin-left: 5px; padding: 5px; padding-right: 16px }'; + var expected = '.rule { font-size: 16px; margin: 5vw; margin-left: 5px; padding: 5px; padding-right: 16px }'; + var options = { + propList: ['*', '!margin-left', '!*padding*', '!font*'] + }; + var processed = postcss(pxToViewport(options)).process(css).css; + + expect(processed).toBe(expected); + }); + + it('should replace all properties when prop list is not given', function () { + var rules = '.rule { margin: 16px; font-size: 15px }'; + var expected = '.rule { margin: 5vw; font-size: 4.6875vw }'; + var processed = postcss(pxToViewport()).process(rules).css; + + expect(processed).toBe(expected); + }); +}); + describe('minPixelValue', function () { it('should not replace values below minPixelValue', function () { var options = { @@ -278,3 +311,54 @@ describe('replace', function () { expect(processed).toBe(expected); }); }); + +describe('filter-prop-list', function () { + it('should find "exact" matches from propList', function () { + var propList = ['font-size', 'margin', '!padding', '*border*', '*', '*y', '!*font*']; + var expected = 'font-size,margin'; + expect(filterPropList.exact(propList).join()).toBe(expected); + }); + + it('should find "contain" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', '*border*', '*', '*y', '!*font*']; + var expected = 'margin,border'; + expect(filterPropList.contain(propList).join()).toBe(expected); + }); + + it('should find "start" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', 'border*', '*', '*y', '!*font*']; + var expected = 'border'; + expect(filterPropList.startWith(propList).join()).toBe(expected); + }); + + it('should find "end" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', 'border*', '*', '*y', '!*font*']; + var expected = 'y'; + expect(filterPropList.endWith(propList).join()).toBe(expected); + }); + + it('should find "not" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', 'border*', '*', '*y', '!*font*']; + var expected = 'padding'; + expect(filterPropList.notExact(propList).join()).toBe(expected); + }); + + it('should find "not contain" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', '!border*', '*', '*y', '!*font*']; + var expected = 'font'; + expect(filterPropList.notContain(propList).join()).toBe(expected); + }); + + it('should find "not start" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', '!border*', '*', '*y', '!*font*']; + var expected = 'border'; + expect(filterPropList.notStartWith(propList).join()).toBe(expected); + }); + + it('should find "not end" matches from propList and reduce to string', function () { + var propList = ['font-size', '*margin*', '!padding', '!border*', '*', '!*y', '!*font*']; + var expected = 'y'; + expect(filterPropList.notEndWith(propList).join()).toBe(expected); + }); +}); + diff --git a/src/prop-list-matcher.js b/src/prop-list-matcher.js new file mode 100644 index 0000000..294830e --- /dev/null +++ b/src/prop-list-matcher.js @@ -0,0 +1,106 @@ +var filterPropList = { + exact: function (list) { + return list.filter(function (m) { + return m.match(/^[^\*\!]+$/); + }); + }, + contain: function (list) { + return list.filter(function (m) { + return m.match(/^\*.+\*$/); + }).map(function (m) { + return m.substr(1, m.length - 2); + }); + }, + endWith: function (list) { + return list.filter(function (m) { + return m.match(/^\*[^\*]+$/); + }).map(function (m) { + return m.substr(1); + }); + }, + startWith: function (list) { + return list.filter(function (m) { + return m.match(/^[^\*\!]+\*$/); + }).map(function (m) { + return m.substr(0, m.length - 1); + }); + }, + notExact: function (list) { + return list.filter(function (m) { + return m.match(/^\![^\*].*$/); + }).map(function (m) { + return m.substr(1); + }); + }, + notContain: function (list) { + return list.filter(function (m) { + return m.match(/^\!\*.+\*$/); + }).map(function (m) { + return m.substr(2, m.length - 3); + }); + }, + notEndWith: function (list) { + return list.filter(function (m) { + return m.match(/^\!\*[^\*]+$/); + }).map(function (m) { + return m.substr(2); + }); + }, + notStartWith: function (list) { + return list.filter(function (m) { + return m.match(/^\![^\*]+\*$/); + }).map(function (m) { + return m.substr(1, m.length - 2); + }); + } +}; + +function createPropListMatcher(propList) { + var hasWild = propList.indexOf('*') > -1; + var matchAll = (hasWild && propList.length === 1); + var lists = { + exact: filterPropList.exact(propList), + contain: filterPropList.contain(propList), + startWith: filterPropList.startWith(propList), + endWith: filterPropList.endWith(propList), + notExact: filterPropList.notExact(propList), + notContain: filterPropList.notContain(propList), + notStartWith: filterPropList.notStartWith(propList), + notEndWith: filterPropList.notEndWith(propList) + }; + return function (prop) { + if (matchAll) return true; + return ( + ( + hasWild || + lists.exact.indexOf(prop) > -1 || + lists.contain.some(function (m) { + return prop.indexOf(m) > -1; + }) || + lists.startWith.some(function (m) { + return prop.indexOf(m) === 0; + }) || + lists.endWith.some(function (m) { + return prop.indexOf(m) === prop.length - m.length; + }) + ) && + !( + lists.notExact.indexOf(prop) > -1 || + lists.notContain.some(function (m) { + return prop.indexOf(m) > -1; + }) || + lists.notStartWith.some(function (m) { + return prop.indexOf(m) === 0; + }) || + lists.notEndWith.some(function (m) { + return prop.indexOf(m) === prop.length - m.length; + }) + ) + ); + }; +} + +module.exports = { + filterPropList, + createPropListMatcher +};