From 9d6c5cee5eea367fb4c7cf0c3bb1117979b5fbf4 Mon Sep 17 00:00:00 2001 From: Dmitry Karpunin Date: Tue, 28 Apr 2020 21:45:02 +0300 Subject: [PATCH] /* px-to-viewport-ignore */ & /* px-to-viewport-ignore-next */ --- .npmignore | 1 + CHANGELOG.md | 21 ++++++- LICENSE | 4 +- README.md | 94 +++++++++++++++++++++++--------- README_CN.md | 88 ++++++++++++++++++++++-------- example/main-viewport.css | 28 ++++++++++ example/main.css | 29 ++++++++++ index.js | 106 +++++++++++++++++++++++------------- spec/px-to-viewport.spec.js | 31 ++++++++++- 9 files changed, 311 insertions(+), 91 deletions(-) diff --git a/.npmignore b/.npmignore index 8747b5a..6e102e6 100755 --- a/.npmignore +++ b/.npmignore @@ -3,3 +3,4 @@ npm-debug.log* node_modules spec +package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c58fb6b..f755fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased 1.2.0] - 2020-xx-xx + +You can install edge-version over +`npm i -D evrone/postcss-px-to-viewport` or `yarn add -D evrone/postcss-px-to-viewport`. + +### Added +- [#50](https://github.com/evrone/postcss-px-to-viewport/pull/50) by [@IceApriler](https://github.com/IceApriler): + `include` (Regexp or Array of Regexp) If `include` is set, only matching files will be converted, + for example, only files under `src/mobile/` (`include: /\/src\/mobile\//`) + > `exclude` and `include` can be set together, and the intersection of the two rules will be taken. +- Added `/* px-to-viewport-ignore */` and `/* px-to-viewport-ignore-next */` — special comments + for ignore conversion of single lines, inspired by + [#27](https://github.com/evrone/postcss-px-to-viewport/pull/27) from [@lcat](https://github.com/lcat) + [Read more about ignoring](https://github.com/evrone/postcss-px-to-viewport#ignoring). + +### Changed +- Changed testing lib to [Jest](https://github.com/facebook/jest) + from [jasmine-node](https://github.com/mhevery/jasmine-node). +- `package-lock.json` included to git-repo. + ## [1.1.1] - 2019-07-08 ### Fixed - - Fixed `rule.source === undefined` from `postcss-modules-values`. ## [1.1.0] - 2019-02-05 diff --git a/LICENSE b/LICENSE index 0dc701c..111f06f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2019 Dmitry Karpunin +Copyright (c) 2016-2020 Dmitry Karpunin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 4f88b96..234df59 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,6 @@ English | [中文](README_CN.md) A plugin for [PostCSS](https://github.com/postcss/postcss) that generates viewport units (vw, vh, vmin, vmax) from pixel units. - - Sponsored by Evrone - - ## Demo If your project involves a fixed width, this script will help to convert pixels into viewport units. @@ -27,6 +22,10 @@ If your project involves a fixed width, this script will help to convert pixels } .class2 { + padding-top: 10px; /* px-to-viewport-ignore */ + /* px-to-viewport-ignore-next */ + padding-bottom: 10px; + /* Any other comment */ border: 1px solid black; margin-bottom: 1px; font-size: 20px; @@ -53,6 +52,9 @@ If your project involves a fixed width, this script will help to convert pixels } .class2 { + padding-top: 10px; + padding-bottom: 10px; + /* Any other comment */ border: 1px solid black; margin-bottom: 1px; font-size: 6.25vw; @@ -120,17 +122,61 @@ Default Options: - `minPixelValue` (Number) Set the minimum pixel value to replace. - `mediaQuery` (Boolean) Allow px to be converted in media queries. - `replace` (Boolean) replaces rules containing vw instead of adding fallbacks. -- `exclude` (Array or Regexp) Ignore some files like 'node_modules' +- `exclude` (Regexp or Array of Regexp) Ignore some files like 'node_modules' - If value is regexp, will ignore the matches files. - If value is array, the elements of the array are regexp. -- `include` (Array or Regexp) If `include` is set, only matching files will be converted, for example, only files under 'src/mobile' +- `include` (Regexp or Array of Regexp) If `include` is set, only matching files will be converted, + for example, only files under `src/mobile/` (`include: /\/src\/mobile\//`) - If the value is regexp, the matching file will be included, otherwise it will be excluded. - If value is array, the elements of the array are regexp. - `landscape` (Boolean) Adds `@media (orientation: landscape)` with values converted via `landscapeWidth`. - `landscapeUnit` (String) Expected unit for `landscape` option - `landscapeWidth` (Number) Viewport width for landscape orientation. -> `exclude` and` include` can be set together, and the intersection of the two rules will be taken. +> `exclude` and `include` can be set together, and the intersection of the two rules will be taken. + +#### Ignoring + +You can use special comments for ignore conversion of single lines: +- `/* px-to-viewport-ignore-next */` — on a separate line, prevents conversion on the next line. +- `/* px-to-viewport-ignore */` — after the property on the right, prevents conversion on the same line. + +Example: +```css +/* example input: */ +.class { + /* px-to-viewport-ignore-next */ + width: 10px; + padding: 10px; + height: 10px; /* px-to-viewport-ignore */ + border: solid 2px #000; /* px-to-viewport-ignore */ +} + +/* example output: */ +.class { + width: 10px; + padding: 3.125vw; + height: 10px; + border: solid 2px #000; +} +``` + +There are several more reasons why your pixels may not convert, the following options may affect this: +`propList`, `selectorBlackList`, `minPixelValue`, `mediaQuery`, `exclude`, `include`. + +#### Use with PostCss configuration file + +add to your `postcss.config.js` +```js +module.exports = { + plugins: { + // ... + 'postcss-px-to-viewport': { + // options + } + } +} +``` #### Use with gulp-postcss @@ -155,19 +201,10 @@ gulp.task('css', function () { }); ``` -#### Use with PostCss configuration file +## Contributing -add to your `postcss.config.js` -```js -module.exports = { - plugins: { - // ... - 'postcss-px-to-viewport': { - // options - } - } -} -``` +Please read [Code of Conduct](CODE-OF-CONDUCT.md) +and [Contributing Guidelines](CONTRIBUTING.md) for submitting pull requests to us. ## Running the tests @@ -180,18 +217,14 @@ Then run the tests via npm script: $ npm run test ``` -## Contributing +## Changelog -Please read [Code of Conduct](CODE-OF-CONDUCT.md) and [Contributing Guidelines](CONTRIBUTING.md) for submitting pull requests to us. +The changelog is [here](CHANGELOG.md). ## Versioning We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/evrone/postcss-px-to-viewport/tags). -## Changelog - -The changelog is [here](CHANGELOG.md). - ## Authors * [Dmitry Karpunin](https://github.com/KODerFunk) - *Initial work* @@ -203,6 +236,15 @@ See also the list of [contributors](https://github.com/evrone/postcss-px-to-view This project is licensed under the [MIT License](LICENSE). +## Sponsors + +Visit [Evrone](https://evrone.com/) website to get more information about the [projects](https://evrone.com/cases) build. + + + Sponsored by Evrone + + ## Acknowledgments * Hat tip to https://github.com/cuth/postcss-pxtorem/ for inspiring us for this project. diff --git a/README_CN.md b/README_CN.md index 510cf34..158f2e4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -5,11 +5,6 @@ 将px单位转换为视口单位的 (vw, vh, vmin, vmax) 的 [PostCSS](https://github.com/postcss/postcss) 插件. - - Sponsored by Evrone - - ## 简介 如果你的样式需要做根据视口大小来调整宽度,这个脚本可以将你CSS中的px单位转化为vw,1vw等于1/100视口宽度。 @@ -27,6 +22,10 @@ } .class2 { + padding-top: 10px; /* px-to-viewport-ignore */ + /* px-to-viewport-ignore-next */ + padding-bottom: 10px; + /* Any other comment */ border: 1px solid black; margin-bottom: 1px; font-size: 20px; @@ -53,6 +52,9 @@ } .class2 { + padding-top: 10px; + padding-bottom: 10px; + /* Any other comment */ border: 1px solid black; margin-bottom: 1px; font-size: 6.25vw; @@ -124,6 +126,7 @@ $ yarn add -D postcss-px-to-viewport - 如果值是一个正则表达式,那么匹配这个正则的文件会被忽略 - 如果传入的值是一个数组,那么数组里的值必须为正则 - `include` (Array or Regexp) 如果设置了`include`,那将只有匹配到的文件才会被转换,例如只转换 'src/mobile' 下的文件 + (`include: /\/src\/mobile\//`) - 如果值是一个正则表达式,将包含匹配的文件,否则将排除该文件 - 如果传入的值是一个数组,那么数组里的值必须为正则 - `landscape` (Boolean) 是否添加根据 `landscapeWidth` 生成的媒体查询条件 `@media (orientation: landscape)` @@ -132,6 +135,49 @@ $ yarn add -D postcss-px-to-viewport > `exclude`和`include`是可以一起设置的,将取两者规则的交集。 +#### Ignoring (需要翻译帮助。) + +You can use special comments for ignore conversion of single lines: +- `/* px-to-viewport-ignore-next */` — on a separate line, prevents conversion on the next line. +- `/* px-to-viewport-ignore */` — after the property on the right, prevents conversion on the same line. + +Example: +```css +/* example input: */ +.class { + /* px-to-viewport-ignore-next */ + width: 10px; + padding: 10px; + height: 10px; /* px-to-viewport-ignore */ + border: solid 2px #000; /* px-to-viewport-ignore */ +} + +/* example output: */ +.class { + width: 10px; + padding: 3.125vw; + height: 10px; + border: solid 2px #000; +} +``` + +There are several more reasons why your pixels may not convert, the following options may affect this: +`propList`, `selectorBlackList`, `minPixelValue`, `mediaQuery`, `exclude`, `include`. + +#### 使用PostCss配置文件时 + +在`postcss.config.js`添加如下配置 +```js +module.exports = { + plugins: { + // ... + 'postcss-px-to-viewport': { + // options + } + } +} +``` + #### 直接在gulp中使用,添加gulp-postcss 在 `gulpfile.js` 添加如下配置: @@ -155,19 +201,10 @@ gulp.task('css', function () { }); ``` -#### 使用PostCss配置文件时 +## 参与贡献 -在`postcss.config.js`添加如下配置 -```js -module.exports = { - plugins: { - // ... - 'postcss-px-to-viewport': { - // options - } - } -} -``` +在提PR之前,请先阅读 [代码指南](CODE-OF-CONDUCT.md) +和 [贡献指南](CONTRIBUTING.md) ## 测试 @@ -180,18 +217,14 @@ $ npm install $ npm run test ``` -## 参与贡献 +## Changelog -在提PR之前,请先阅读 [代码指南](CODE-OF-CONDUCT.md) 和 [贡献指南](CONTRIBUTING.md) +变更日志在 [这](CHANGELOG.md). ## 版本跟踪 使用 [SemVer](http://semver.org/) 做版本跟踪, 可用版本可在[这](https://github.com/evrone/postcss-px-to-viewport/tags)看到 -## Changelog - -变更日志在 [这](CHANGELOG.md). - ## 作者 * [Dmitry Karpunin](https://github.com/KODerFunk) - *Initial work* @@ -203,6 +236,15 @@ $ npm run test 本项目使用 [MIT License](LICENSE). +## 赞助商 + +访问 [Evrone](https://evrone.com/)网站以获取有关[项目构建](https://evrone.com/cases)的更多信息。 + + + Sponsored by Evrone + + ## 借鉴自 * 受 https://github.com/cuth/postcss-pxtorem/ 启发有了这个项目 diff --git a/example/main-viewport.css b/example/main-viewport.css index 60afa2c..9451323 100644 --- a/example/main-viewport.css +++ b/example/main-viewport.css @@ -19,6 +19,34 @@ } } +.class4-ignore::before { + content: ''; + width: 10px; + padding: 3.125vw; + height: 10px; + border: solid 2px #000; +} +.class5-bad-ignore { + /* px-to-viewport-ignore */ + width: 3.125vw; + /* px-to-viewport-ignore */ + height: 3.125vw; + /* px-to-viewport-ignore-next */ +} + +@keyframes move { + 0% { + transform: translate(0, 0); + } + /* px-to-viewport-ignore-next */ + 50% { + transform: translate(10px, -10px); + } + 100% { + transform: translate(10px, -10px); /* px-to-viewport-ignore */ + } +} + /* .class { font-size: 16px; diff --git a/example/main.css b/example/main.css index 0f3d1ac..b4c4c4e 100755 --- a/example/main.css +++ b/example/main.css @@ -19,6 +19,35 @@ } } +.class4-ignore::before { + content: ''; + /* px-to-viewport-ignore-next */ + width: 10px; + padding: 10px; + height: 10px; /* px-to-viewport-ignore */ + border: solid 2px #000; /* px-to-viewport-ignore */ +} +.class5-bad-ignore { + /* px-to-viewport-ignore */ + width: 10px; + /* px-to-viewport-ignore */ + height: 10px; + /* px-to-viewport-ignore-next */ +} + +@keyframes move { + 0% { + transform: translate(0, 0); + } + /* px-to-viewport-ignore-next */ + 50% { + transform: translate(10px, -10px); + } + 100% { + transform: translate(10px, -10px); /* px-to-viewport-ignore */ + } +} + /* .class { font-size: 16px; diff --git a/index.js b/index.js index 8b0edcb..6419322 100755 --- a/index.js +++ b/index.js @@ -22,45 +22,49 @@ var defaults = { landscapeWidth: 568 }; +var ignoreNextComment = 'px-to-viewport-ignore-next'; +var ignorePrevComment = 'px-to-viewport-ignore'; + module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { - var opts = objectAssign({}, defaults, options); + checkRegExpOrArray(opts, 'exclude'); + checkRegExpOrArray(opts, 'include'); + var pxRegex = getUnitRegexp(opts.unitToConvert); var satisfyPropList = createPropListMatcher(opts.propList); var landscapeRules = []; - - return function (css) { + + return function (css, result) { css.walkRules(function (rule) { // Add exclude option to ignore some files like 'node_modules' var file = rule.source && rule.source.input.file; if (opts.include && file) { if (Object.prototype.toString.call(opts.include) === '[object RegExp]') { - if (!isInclude(opts.include, file)) return; + if (!opts.include.test(file)) return; } else if (Object.prototype.toString.call(opts.include) === '[object Array]') { var flag = false; - for (let i = 0; i < opts.include.length; i++) { - if (isInclude(opts.include[i], file)) flag = true; + for (var i = 0; i < opts.include.length; i++) { + if (opts.include[i].test(file)) { + flag = true; + break; + } } if (!flag) return; - } else { - throw new Error('options.include should be RegExp or Array.'); } } if (opts.exclude && file) { if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') { - if (isExclude(opts.exclude, file)) return; + if (opts.exclude.test(file)) return; } else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') { - for (let i = 0; i < opts.exclude.length; i++) { - if (isExclude(opts.exclude[i], file)) return; + for (var i = 0; i < opts.exclude.length; i++) { + if (opts.exclude[i].test(file)) return; } - } else { - throw new Error('options.exclude should be RegExp or Array.'); } } - + if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return; if (opts.landscape && !rule.parent.params) { @@ -69,27 +73,46 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { 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, opts.landscapeWidth)) })); }); - + if (landscapeRule.nodes.length > 0) { - landscapeRules.push(landscapeRule); + 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 prev = decl.prev(); + // prev declaration is ignore conversion comment at same line + if (prev && prev.type === 'comment' && prev.text === ignoreNextComment) { + // remove comment + prev.remove(); + return; + } + var next = decl.next(); + // next declaration is ignore conversion comment at same line + if (next && next.type === 'comment' && next.text === ignorePrevComment) { + if (/\n/.test(next.raws.before)) { + result.warn('Unexpected comment /* ' + ignorePrevComment + ' */ must be after declaration at same line.', { node: next }); + } else { + // remove comment + next.remove(); + return; + } + } + var unit; var size; var params = rule.parent.params; - + if (opts.landscape && params && params.indexOf('landscape') !== -1) { unit = opts.landscapeUnit; size = opts.landscapeWidth; @@ -97,11 +120,11 @@ module.exports = postcss.plugin('postcss-px-to-viewport', function (options) { 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; - + if (opts.replace) { decl.value = value; } else { @@ -109,10 +132,10 @@ 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(function(rule) { landscapeRoot.append(rule); }); @@ -135,6 +158,27 @@ function createPxReplace(opts, viewportUnit, viewportSize) { }; } +function error(decl, message) { + throw decl.error(message, { plugin: 'postcss-px-to-viewport' }); +} + +function checkRegExpOrArray(options, optionName) { + var option = options[optionName]; + if (!option) return; + if (Object.prototype.toString.call(option) === '[object RegExp]') return; + if (Object.prototype.toString.call(option) === '[object Array]') { + var bad = false; + for (var i = 0; i < option.length; i++) { + if (Object.prototype.toString.call(option[i]) !== '[object RegExp]') { + bad = true; + break; + } + } + if (!bad) return; + } + throw new Error('options.' + optionName + ' should be RegExp or Array of RegExp.'); +} + function toFixed(number, precision) { var multiplier = Math.pow(10, precision + 1), wholeNumber = Math.floor(number * multiplier); @@ -149,20 +193,6 @@ 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 isInclude(reg, file) { - if (Object.prototype.toString.call(reg) !== '[object RegExp]') { - throw new Error('options.include 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); diff --git a/spec/px-to-viewport.spec.js b/spec/px-to-viewport.spec.js index 1b7afe5..0e47c10 100644 --- a/spec/px-to-viewport.spec.js +++ b/spec/px-to-viewport.spec.js @@ -605,5 +605,34 @@ describe('landscape', function() { var expected = '.rule { font-size: 15vw }'; expect(processed).toBe(expected); - }) + }); +}); + +describe('/* px-to-viewport-ignore */ & /* px-to-viewport-ignore-next */', function() { + it('should ignore right-commented', function() { + var css = '.rule { font-size: 15px; /* simple comment */ width: 100px; /* px-to-viewport-ignore */ height: 50px; }'; + var expected = '.rule { font-size: 4.6875vw; /* simple comment */ width: 100px; height: 15.625vw; }'; + + var processed = postcss(pxToViewport()).process(css).css; + + expect(processed).toBe(expected); + }); + + it('should ignore right-commented in multiline-css', function() { + var css = '.rule {\n font-size: 15px;\n width: 100px; /*px-to-viewport-ignore*/\n height: 50px;\n}'; + var expected = '.rule {\n font-size: 4.6875vw;\n width: 100px;\n height: 15.625vw;\n}'; + + var processed = postcss(pxToViewport()).process(css).css; + + expect(processed).toBe(expected); + }); + + it('should ignore before-commented in multiline-css', function() { + var css = '.rule {\n font-size: 15px;\n /*px-to-viewport-ignore-next*/\n width: 100px;\n /*px-to-viewport-ignore*/\n height: 50px;\n}'; + var expected = '.rule {\n font-size: 4.6875vw;\n width: 100px;\n /*px-to-viewport-ignore*/\n height: 15.625vw;\n}'; + + var processed = postcss(pxToViewport()).process(css).css; + + expect(processed).toBe(expected); + }); });