Browse Source

change mediaQuery option to replace props inside rules instead of params

add-media-landscape
Ivan Bunin 7 years ago
parent
commit
b27637ad75
  1. 1
      .gitignore
  2. 6
      example/index.js
  3. 4
      example/main-viewport.css
  4. 76
      index.js
  5. 155
      spec/px-to-viewport.spec.js
  6. 14
      src/pixel-unit-regexp.js
  7. 152
      src/prop-list-matcher.js

1
.gitignore

@ -3,4 +3,5 @@ logs
*.log *.log
npm-debug.log* npm-debug.log*
node_modules node_modules
.idea
postcss-px-to-em-master postcss-px-to-em-master

6
example/index.js

@ -4,10 +4,8 @@ var fs = require('fs');
var postcss = require('postcss'); var postcss = require('postcss');
var pxToViewport = require('..'); var pxToViewport = require('..');
var css = fs.readFileSync('main.css', 'utf8'); 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) { fs.writeFile('main-viewport.css', processedCss, function (err) {
if (err) { if (err) {

4
example/main-viewport.css

@ -14,8 +14,8 @@
} }
@media (min-width: 750px) { @media (min-width: 750px) {
.class3 { .class3 {
font-size: 5vw;
line-height: 6.875vw;
font-size: 16px;
line-height: 22px;
} }
} }

76
index.js

@ -3,6 +3,7 @@
var postcss = require('postcss'); var postcss = require('postcss');
var objectAssign = require('object-assign'); var objectAssign = require('object-assign');
var { createPropListMatcher } = require('./src/prop-list-matcher'); var { createPropListMatcher } = require('./src/prop-list-matcher');
var { getUnitRegexp } = require('./src/pixel-unit-regexp');
var defaults = { var defaults = {
unitToConvert: 'px', unitToConvert: 'px',
@ -21,68 +22,53 @@ var defaults = {
module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { module.exports = postcss.plugin('postcss-px-to-viewport', function (options) {
var opts = objectAssign({}, defaults, 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); var satisfyPropList = createPropListMatcher(opts.propList);
return function (css) { 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 (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]') { } 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 { } 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) { function handleExclude (reg, file) {
if (Object.prototype.toString.call(reg) !== '[object RegExp]') { 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) { function getUnit(prop, opts) {
@ -114,7 +100,7 @@ function blacklistedSelector(blacklist, selector) {
} }
function declarationExists(decls, prop, value) { 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);
});
} }

155
spec/px-to-viewport.spec.js

@ -46,11 +46,18 @@ describe('px-to-viewport', function() {
}); });
it('should not add properties that already exist', 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() { describe('value parsing', function() {
@ -114,7 +121,7 @@ describe('viewportWidth', function() {
var expected = '.rule { font-size: 3.125vw }'; var expected = '.rule { font-size: 3.125vw }';
var options = { var options = {
viewportWidth: 480 viewportWidth: 480
}
};
var processed = postcss(pxToViewport(options)).process(basicCSS).css; var processed = postcss(pxToViewport(options)).process(basicCSS).css;
expect(processed).toBe(expected); expect(processed).toBe(expected);
@ -123,13 +130,13 @@ describe('viewportWidth', function() {
describe('unitPrecision', function () { describe('unitPrecision', function () {
it('should replace using a decimal of 2 places', 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 () { describe('selectorBlackList', function () {
it('should ignore selectors in the selector black list', 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 () { 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 () { 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 () { 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 () { describe('propList', function () {
it('should only replace properties in the prop list', 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 () { 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 () { 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 () { describe('minPixelValue', function () {
it('should not replace values below 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 () { describe('exclude', function () {
var rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; 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 () { it('when using regex at the time, the style should not be overwritten.', function () {
var options = { var options = {
exclude: /node_modules/ exclude: /node_modules/
}
};
var processed = postcss(pxToViewport(options)).process(rules, { var processed = postcss(pxToViewport(options)).process(rules, {
from: '/node_modules/main.css' from: '/node_modules/main.css'
}).css; }).css;
@ -269,7 +286,7 @@ describe('exclude', function () {
it('when using regex at the time, the style should be overwritten.', function () { it('when using regex at the time, the style should be overwritten.', function () {
var options = { var options = {
exclude: /node_modules/ exclude: /node_modules/
}
};
var processed = postcss(pxToViewport(options)).process(rules, { var processed = postcss(pxToViewport(options)).process(rules, {
from: '/example/main.css' from: '/example/main.css'
}).css; }).css;
@ -280,7 +297,7 @@ describe('exclude', function () {
it('when using array at the time, the style should not be overwritten.', function () { it('when using array at the time, the style should not be overwritten.', function () {
var options = { var options = {
exclude: [/node_modules/, /exclude/] exclude: [/node_modules/, /exclude/]
}
};
var processed = postcss(pxToViewport(options)).process(rules, { var processed = postcss(pxToViewport(options)).process(rules, {
from: '/exclude/main.css' from: '/exclude/main.css'
}).css; }).css;
@ -291,7 +308,7 @@ describe('exclude', function () {
it('when using array at the time, the style should be overwritten.', function () { it('when using array at the time, the style should be overwritten.', function () {
var options = { var options = {
exclude: [/node_modules/, /exclude/] exclude: [/node_modules/, /exclude/]
}
};
var processed = postcss(pxToViewport(options)).process(rules, { var processed = postcss(pxToViewport(options)).process(rules, {
from: '/example/main.css' from: '/example/main.css'
}).css; }).css;

14
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
};

152
src/prop-list-matcher.js

@ -1,57 +1,57 @@
var filterPropList = { var filterPropList = {
exact: function (list) { exact: function (list) {
return list.filter(function (m) {
return m.match(/^[^\*\!]+$/);
});
return list.filter(function (m) {
return m.match(/^[^\*\!]+$/);
});
}, },
contain: function (list) { 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) { 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) { 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) { 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) { 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) { 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) { 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 hasWild = propList.indexOf('*') > -1;
var matchAll = (hasWild && propList.length === 1); var matchAll = (hasWild && propList.length === 1);
var lists = { 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) { 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 = { module.exports = {
filterPropList,
createPropListMatcher
filterPropList,
createPropListMatcher
}; };
Loading…
Cancel
Save