diff --git a/.gitignore b/.gitignore index 7f5dd6e..1f9e97f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ logs *.log npm-debug.log* node_modules +.idea postcss-px-to-em-master diff --git a/example/index.js b/example/index.js index 6d10bde..476874e 100755 --- a/example/index.js +++ b/example/index.js @@ -4,10 +4,8 @@ var fs = require('fs'); var postcss = require('postcss'); var pxToViewport = require('..'); var css = fs.readFileSync('main.css', 'utf8'); -var options = { - replace: false -}; -var processedCss = postcss(pxToViewport(options)).process(css).css; + +var processedCss = postcss(pxToViewport()).process(css).css; fs.writeFile('main-viewport.css', processedCss, function (err) { if (err) { diff --git a/example/main-viewport.css b/example/main-viewport.css index 43c31b0..60afa2c 100644 --- a/example/main-viewport.css +++ b/example/main-viewport.css @@ -14,8 +14,8 @@ } @media (min-width: 750px) { .class3 { - font-size: 5vw; - line-height: 6.875vw; + font-size: 16px; + line-height: 22px; } } diff --git a/index.js b/index.js index 7e3be9f..b130e2d 100755 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ var postcss = require('postcss'); var objectAssign = require('object-assign'); var { createPropListMatcher } = require('./src/prop-list-matcher'); +var { getUnitRegexp } = require('./src/pixel-unit-regexp'); var defaults = { unitToConvert: 'px', @@ -21,68 +22,53 @@ var defaults = { 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); - - // excluding regex trick: http://www.rexegg.com/regex-best-trick.html - // Not anything inside double quotes - // Not anything inside single quotes - // 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 = getUnitRegexp(opts.unitToConvert); var satisfyPropList = createPropListMatcher(opts.propList); return function (css) { - - css.walkDecls(function (decl, i) { + css.walkRules(function (rule) { + // Add exclude option to ignore some files like 'node_modules' + var file = rule.source.input.file; - // Add exlclude option to ignore some files like 'node_modules' - if (opts.exclude && decl.source.input.file) { + if (opts.exclude && file) { if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') { - if (!handleExclude(opts.exclude, decl.source.input.file)) return; + if (!handleExclude(opts.exclude, file)) return; } else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') { - for (let i = 0; i < opts.exclude.length; ++i) { - if (!handleExclude(opts.exclude[i], decl.source.input.file)) return; + for (let i = 0; i < opts.exclude.length; i++) { + if (!handleExclude(opts.exclude[i], file)) return; } } else { - throw new Error('options.exclude should be RegExp or Array!'); + throw new Error('options.exclude should be RegExp or Array.'); } } + + if (rule.parent.params && !opts.mediaQuery) return; + if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return; + + rule.walkDecls(function(decl, i) { + if (decl.value.indexOf(opts.unitToConvert) === -1) return; + if (!satisfyPropList(decl.prop)) 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)); - - if (declarationExists(decl.parent, decl.prop, value)) return; + var unit = getUnit(decl.prop, opts); + var value = decl.value.replace(pxRegex, createPxReplace(opts.viewportWidth, opts.minPixelValue, opts.unitPrecision, unit)); - if (opts.replace) { - decl.value = value; - } else { - decl.parent.insertAfter(i, decl.clone({ value: value })); - } - }); + if (declarationExists(decl.parent, decl.prop, value)) return; - if (opts.mediaQuery) { - css.walkAtRules('media', function (rule) { - if (rule.params.indexOf(opts.unitToConvert) === -1) return; - rule.params = rule.params.replace(pxRegex, pxReplace); + if (opts.replace) { + decl.value = value; + } else { + decl.parent.insertAfter(i, decl.clone({ value: value })); + } }); - } - + }); }; }); function handleExclude (reg, file) { if (Object.prototype.toString.call(reg) !== '[object RegExp]') { - throw new Error('options.exclude should be RegExp!'); + throw new Error('options.exclude should be RegExp.'); } - if (file.match(reg) !== null) return false; - return true; + return file.match(reg) === null; } function getUnit(prop, opts) { @@ -114,7 +100,7 @@ function blacklistedSelector(blacklist, selector) { } function declarationExists(decls, prop, value) { - return decls.some(function (decl) { - return (decl.prop === prop && decl.value === value); - }); + return decls.some(function (decl) { + return (decl.prop === prop && decl.value === value); + }); } \ No newline at end of file diff --git a/spec/px-to-viewport.spec.js b/spec/px-to-viewport.spec.js index 3f3fa54..dab0b51 100644 --- a/spec/px-to-viewport.spec.js +++ b/spec/px-to-viewport.spec.js @@ -46,11 +46,18 @@ describe('px-to-viewport', function() { }); it('should not add properties that already exist', function () { - var expected = '.rule { font-size: 16px; font-size: 5vw; }'; - var processed = postcss(pxToViewport()).process(expected).css; + var expected = '.rule { font-size: 16px; font-size: 5vw; }'; + var processed = postcss(pxToViewport()).process(expected).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); + + it('should not replace units inside mediaQueries by default', function() { + var expected = '@media (min-width: 500px) { .rule { font-size: 16px } }'; + var processed = postcss(pxToViewport()).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; + + expect(processed).toBe(expected); + }) }); describe('value parsing', function() { @@ -114,7 +121,7 @@ describe('viewportWidth', function() { var expected = '.rule { font-size: 3.125vw }'; var options = { viewportWidth: 480 - } + }; var processed = postcss(pxToViewport(options)).process(basicCSS).css; expect(processed).toBe(expected); @@ -123,13 +130,13 @@ describe('viewportWidth', function() { describe('unitPrecision', function () { it('should replace using a decimal of 2 places', function () { - var expected = '.rule { font-size: 4.69vw }'; - var options = { - unitPrecision: 2 - }; - var processed = postcss(pxToViewport(options)).process(basicCSS).css; + var expected = '.rule { font-size: 4.69vw }'; + var options = { + unitPrecision: 2 + }; + var processed = postcss(pxToViewport(options)).process(basicCSS).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); }); @@ -161,104 +168,114 @@ describe('fontViewportUnit', function() { describe('selectorBlackList', function () { it('should ignore selectors in the selector black list', function () { - var rules = '.rule { font-size: 15px } .rule2 { font-size: 15px }'; - var expected = '.rule { font-size: 4.6875vw } .rule2 { font-size: 15px }'; - var options = { - selectorBlackList: ['.rule2'] - }; - var processed = postcss(pxToViewport(options)).process(rules).css; + var rules = '.rule { font-size: 15px } .rule2 { font-size: 15px }'; + var expected = '.rule { font-size: 4.6875vw } .rule2 { font-size: 15px }'; + var options = { + selectorBlackList: ['.rule2'] + }; + var processed = postcss(pxToViewport(options)).process(rules).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); it('should ignore every selector with `body$`', function () { - var rules = 'body { font-size: 16px; } .class-body$ { font-size: 16px; } .simple-class { font-size: 16px; }'; - var expected = 'body { font-size: 5vw; } .class-body$ { font-size: 16px; } .simple-class { font-size: 5vw; }'; - var options = { - selectorBlackList: ['body$'] - }; - var processed = postcss(pxToViewport(options)).process(rules).css; + var rules = 'body { font-size: 16px; } .class-body$ { font-size: 16px; } .simple-class { font-size: 16px; }'; + var expected = 'body { font-size: 5vw; } .class-body$ { font-size: 16px; } .simple-class { font-size: 5vw; }'; + var options = { + selectorBlackList: ['body$'] + }; + var processed = postcss(pxToViewport(options)).process(rules).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); it('should only ignore exactly `body`', function () { - var rules = 'body { font-size: 16px; } .class-body { font-size: 16px; } .simple-class { font-size: 16px; }'; - var expected = 'body { font-size: 16px; } .class-body { font-size: 5vw; } .simple-class { font-size: 5vw; }'; - var options = { - selectorBlackList: [/^body$/] - }; - var processed = postcss(pxToViewport(options)).process(rules).css; + var rules = 'body { font-size: 16px; } .class-body { font-size: 16px; } .simple-class { font-size: 16px; }'; + var expected = 'body { font-size: 16px; } .class-body { font-size: 5vw; } .simple-class { font-size: 5vw; }'; + var options = { + selectorBlackList: [/^body$/] + }; + var processed = postcss(pxToViewport(options)).process(rules).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); }); describe('mediaQuery', function () { - it('should replace px in media queries', function () { - var options = { - mediaQuery: true - }; - var processed = postcss(pxToViewport(options)).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; - var expected = '@media (min-width: 156.25vw) { .rule { font-size: 5vw } }'; + it('should replace px inside media queries if opts.mediaQuery', function() { + var options = { + mediaQuery: true + }; + var processed = postcss(pxToViewport(options)).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; + var expected = '@media (min-width: 500px) { .rule { font-size: 5vw } }'; - expect(processed).toBe(expected); + expect(processed).toBe(expected); + }); + + it('should not replace px inside media queries if not opts.mediaQuery', function() { + var options = { + mediaQuery: false + }; + var processed = postcss(pxToViewport(options)).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; + var expected = '@media (min-width: 500px) { .rule { font-size: 16px } }'; + + expect(processed).toBe(expected); }); }); 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; + 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); + 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; + 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); + 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; + 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); + expect(processed).toBe(expected); }); }); describe('minPixelValue', function () { it('should not replace values below minPixelValue', function () { - var options = { - propWhiteList: [], - minPixelValue: 2 - }; - var rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; - var expected = '.rule { border: 1px solid #000; font-size: 5vw; margin: 1px 3.125vw; }'; - var processed = postcss(pxToViewport(options)).process(rules).css; + var options = { + propWhiteList: [], + minPixelValue: 2 + }; + var rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; + var expected = '.rule { border: 1px solid #000; font-size: 5vw; margin: 1px 3.125vw; }'; + var processed = postcss(pxToViewport(options)).process(rules).css; - expect(processed).toBe(expected); + expect(processed).toBe(expected); }); }); describe('exclude', function () { var rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; - var covered = '.rule { border: 1px solid #000; font-size: 5vw; margin: 1px 3.125vw; }' + var covered = '.rule { border: 1px solid #000; font-size: 5vw; margin: 1px 3.125vw; }'; it('when using regex at the time, the style should not be overwritten.', function () { var options = { exclude: /node_modules/ - } + }; var processed = postcss(pxToViewport(options)).process(rules, { from: '/node_modules/main.css' }).css; @@ -269,7 +286,7 @@ describe('exclude', function () { it('when using regex at the time, the style should be overwritten.', function () { var options = { exclude: /node_modules/ - } + }; var processed = postcss(pxToViewport(options)).process(rules, { from: '/example/main.css' }).css; @@ -280,7 +297,7 @@ describe('exclude', function () { it('when using array at the time, the style should not be overwritten.', function () { var options = { exclude: [/node_modules/, /exclude/] - } + }; var processed = postcss(pxToViewport(options)).process(rules, { from: '/exclude/main.css' }).css; @@ -291,7 +308,7 @@ describe('exclude', function () { it('when using array at the time, the style should be overwritten.', function () { var options = { exclude: [/node_modules/, /exclude/] - } + }; var processed = postcss(pxToViewport(options)).process(rules, { from: '/example/main.css' }).css; diff --git a/src/pixel-unit-regexp.js b/src/pixel-unit-regexp.js new file mode 100644 index 0000000..f166fb0 --- /dev/null +++ b/src/pixel-unit-regexp.js @@ -0,0 +1,14 @@ +// excluding regex trick: http://www.rexegg.com/regex-best-trick.html + +// Not anything inside double quotes +// Not anything inside single quotes +// Not anything inside url() +// Any digit followed by px +// !singlequotes|!doublequotes|!url()|pixelunit +function getUnitRegexp(unit) { + return new RegExp('"[^"]+"|\'[^\']+\'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)' + unit, 'g'); +} + +module.exports = { + getUnitRegexp +}; diff --git a/src/prop-list-matcher.js b/src/prop-list-matcher.js index 294830e..4855ba0 100644 --- a/src/prop-list-matcher.js +++ b/src/prop-list-matcher.js @@ -1,57 +1,57 @@ var filterPropList = { exact: function (list) { - return list.filter(function (m) { - return m.match(/^[^\*\!]+$/); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + return list.filter(function (m) { + return m.match(/^\![^\*]+\*$/); + }).map(function (m) { + return m.substr(1, m.length - 2); + }); } }; @@ -59,48 +59,48 @@ 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) + 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; - }) - ) - ); + 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 + filterPropList, + createPropListMatcher };