diff --git a/index.js b/index.js index d2cb976..ece5378 100755 --- a/index.js +++ b/index.js @@ -18,12 +18,14 @@ var defaults = { mediaQuery: false, replace: true, landscape: false, - landscapeUnit: 'vh' + landscapeUnit: 'vw', + landscapeWidth: 568 }; module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { var opts = objectAssign({}, defaults, options); + var pxRegex = getUnitRegexp(opts.unitToConvert); var satisfyPropList = createPropListMatcher(opts.propList); var landscapeRules = []; @@ -32,42 +34,63 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { css.walkRules(function (rule) { // Add exclude option to ignore some files like 'node_modules' var file = rule.source.input.file; - + if (opts.exclude && file) { if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') { - if (!handleExclude(opts.exclude, file)) return; + if (isExclude(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], file)) return; + if (isExclude(opts.exclude[i], file)) return; } } else { throw new Error('options.exclude should be RegExp or Array.'); } } - if (!validateParams(rule.parent.params, opts.mediaQuery)) return; if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return; - + if (opts.landscape && !rule.parent.params) { var landscapeRule = rule.clone().removeAll(); - landscapeRules.push(landscapeRule); - + rule.walkDecls(function(decl) { if (decl.value.indexOf(opts.unitToConvert) === -1) return; if (!satisfyPropList(decl.prop)) return; - landscapeRule.append(decl.clone({ - value: decl.value.replace(pxRegex, createPxReplace(opts, opts.landscapeUnit)) - })); + var landscapeDecl = decl.clone({ + value: decl.value.replace(pxRegex, createPxReplace(opts, opts.landscapeUnit, opts.landscapeWidth)) + }); + + if (opts.replace) { + landscapeRule.append(landscapeDecl); + } else { + landscapeRule.append([decl.clone(), landscapeDecl]); + } }); + + if (landscapeRule.nodes.length > 0) { + landscapeRules.push(landscapeRule); + } } + + if (!validateParams(rule.parent.params, opts.mediaQuery)) return; rule.walkDecls(function(decl, i) { if (decl.value.indexOf(opts.unitToConvert) === -1) return; if (!satisfyPropList(decl.prop)) return; - var unit = getUnit(decl.prop, opts); - var value = decl.value.replace(pxRegex, createPxReplace(opts, unit)); + var unit; + var size; + var params = rule.parent.params; + + if (opts.landscape && params && params.indexOf('landscape') !== -1) { + unit = opts.landscapeUnit; + size = opts.landscapeWidth; + } else { + unit = getUnit(decl.prop, opts); + size = opts.viewportWidth; + } + + var value = decl.value.replace(pxRegex, createPxReplace(opts, unit, size)); if (declarationExists(decl.parent, decl.prop, value)) return; @@ -82,29 +105,24 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { if (landscapeRules.length > 0) { var landscapeRoot = new postcss.atRule({ params: '(orientation: landscape)', name: 'media' }); - landscapeRules.forEach(rule => landscapeRoot.append(rule)); + landscapeRules.forEach(function(rule) { + landscapeRoot.append(rule); + }); css.append(landscapeRoot); } }; }); -function handleExclude (reg, file) { - if (Object.prototype.toString.call(reg) !== '[object RegExp]') { - throw new Error('options.exclude should be RegExp.'); - } - return file.match(reg) === null; -} - function getUnit(prop, opts) { return prop.indexOf('font') === -1 ? opts.viewportUnit : opts.fontViewportUnit; } -function createPxReplace(opts, viewportUnit) { +function createPxReplace(opts, viewportUnit, viewportSize) { return function (m, $1) { if (!$1) return m; var pixels = parseFloat($1); if (pixels <= opts.minPixelValue) return m; - var parsedVal = toFixed((pixels / opts.viewportWidth * 100), opts.unitPrecision); + var parsedVal = toFixed((pixels / viewportSize * 100), opts.unitPrecision); return parsedVal === 0 ? '0' : parsedVal + viewportUnit; }; } @@ -123,6 +141,13 @@ function blacklistedSelector(blacklist, selector) { }); } +function isExclude(reg, file) { + if (Object.prototype.toString.call(reg) !== '[object RegExp]') { + throw new Error('options.exclude should be RegExp.'); + } + return file.match(reg) !== null; +} + function declarationExists(decls, prop, value) { return decls.some(function (decl) { return (decl.prop === prop && decl.value === value); @@ -130,5 +155,5 @@ function declarationExists(decls, prop, value) { } function validateParams(params, mediaQuery) { - return !params || (params && mediaQuery && params.indexOf('landscape') === -1); + return !params || (params && mediaQuery); } diff --git a/spec/px-to-viewport.spec.js b/spec/px-to-viewport.spec.js index d327259..6e5235d 100644 --- a/spec/px-to-viewport.spec.js +++ b/spec/px-to-viewport.spec.js @@ -222,12 +222,13 @@ describe('mediaQuery', function () { expect(processed).toBe(expected); }); - it('should not replace px inside media queries if it has params orientation landscape', function() { + it('should replace px inside media queries if it has params orientation landscape and landscape option', function() { var options = { - mediaQuery: true + mediaQuery: true, + landscape: true }; var processed = postcss(pxToViewport(options)).process('@media (orientation-landscape) and (min-width: 500px) { .rule { font-size: 16px } }').css; - var expected = '@media (orientation-landscape) and (min-width: 500px) { .rule { font-size: 16px } }'; + var expected = '@media (orientation-landscape) and (min-width: 500px) { .rule { font-size: 2.8169vw } }'; expect(processed).toBe(expected); }); @@ -392,7 +393,7 @@ describe('filter-prop-list', function () { describe('landscape', function() { it('should add landscape atRule', 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: 1.5625vw; padding: 1.5625vw; padding-right: 5vw }@media (orientation: landscape) {.rule { font-size: 5vh; margin: 5vh; margin-left: 1.5625vh; padding: 1.5625vh; padding-right: 5vh } }'; + var expected = '.rule { font-size: 5vw; margin: 5vw; margin-left: 1.5625vw; padding: 1.5625vw; padding-right: 5vw }@media (orientation: landscape) {.rule { font-size: 2.8169vw; margin: 2.8169vw; margin-left: 0.88028vw; padding: 0.88028vw; padding-right: 2.8169vw } }'; var options = { landscape: true }; @@ -403,10 +404,10 @@ describe('landscape', function() { it('should add landscape atRule with specified landscapeUnits', 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: 1.5625vw; padding: 1.5625vw; padding-right: 5vw }@media (orientation: landscape) {.rule { font-size: 5vw; margin: 5vw; margin-left: 1.5625vw; padding: 1.5625vw; padding-right: 5vw } }'; + var expected = '.rule { font-size: 5vw; margin: 5vw; margin-left: 1.5625vw; padding: 1.5625vw; padding-right: 5vw }@media (orientation: landscape) {.rule { font-size: 2.8169vh; margin: 2.8169vh; margin-left: 0.88028vh; padding: 0.88028vh; padding-right: 2.8169vh } }'; var options = { landscape: true, - landscapeUnit: 'vw' + landscapeUnit: 'vh' }; var processed = postcss(pxToViewport(options)).process(css).css; @@ -425,13 +426,35 @@ describe('landscape', function() { expect(processed).toBe(expected); }); - it('should not replace values inside landscape atRule', function() { + it('should replace values inside landscape atRule', function() { var options = { replace: false, landscape: true }; var processed = postcss(pxToViewport(options)).process(basicCSS).css; - var expected = '.rule { font-size: 15px; font-size: 4.6875vw }@media (orientation: landscape) {.rule { font-size: 4.6875vh } }'; + var expected = '.rule { font-size: 15px; font-size: 4.6875vw }@media (orientation: landscape) {.rule { font-size: 15px; font-size: 2.64085vw } }'; + + expect(processed).toBe(expected); + }); + + it('should add landscape atRule with specified landscapeWidth', function() { + var options = { + landscape: true, + landscapeWidth: 768 + }; + var processed = postcss(pxToViewport(options)).process(basicCSS).css; + var expected = '.rule { font-size: 4.6875vw }@media (orientation: landscape) {.rule { font-size: 1.95313vw } }'; + + expect(processed).toBe(expected); + }); + + it('should not add landscape atRule if it has no nodes', function() { + var css = '.rule { font-size: 15vw }'; + var options = { + landscape: true + }; + var processed = postcss(pxToViewport(options)).process(css).css; + var expected = '.rule { font-size: 15vw }'; expect(processed).toBe(expected); })