├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── HISTORY.md ├── Makefile ├── README.md ├── index.js ├── package.json ├── src └── index.js └── test └── index-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "stage-0", 4 | "es2015" 5 | ], 6 | "plugins": [ 7 | "add-module-exports" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint-config-airbnb/base", 4 | "rules": { 5 | "no-console": [0], 6 | "max-len" : [0] 7 | }, 8 | "ecmaFeatures": { 9 | "experimentalObjectRestSpread": true 10 | }, 11 | "env": { 12 | "node": true, 13 | "mocha": true 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | npm-debug.log 4 | node_modules/* 5 | coverage 6 | lib -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4" 5 | - "5" 6 | 7 | 8 | after_success: 9 | - npm run coveralls 10 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | HISTORY 2 | 3 | --------- 4 | 5 | ### 0.7.0 - 2016/6/23 6 | 7 | - feat: support multi(px/rpx) units transform to rem unit 8 | 9 | ### 0.6.0 - 2016/6/23 10 | 11 | - feat: add option propBlackList 12 | 13 | ### 0.5.0 - 2016/6/21 14 | 15 | :sparkles: 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | publish: 2 | npm run compile 3 | npm publish 4 | 5 | publish-sync: publish 6 | cnpm sync 7 | tnpm sync -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-plugin-px2rem 2 | 3 | [![NPM version](https://img.shields.io/npm/v/postcss-plugin-px2rem.svg?style=flat)](https://npmjs.org/package/postcss-plugin-px2rem) 4 | [![Build Status](https://img.shields.io/travis/ant-tool/postcss-plugin-px2rem.svg?style=flat)](https://travis-ci.org/ant-tool/postcss-plugin-px2rem) 5 | [![Coverage Status](https://img.shields.io/coveralls/ant-tool/postcss-plugin-px2rem.svg?style=flat)](https://coveralls.io/r/ant-tool/postcss-plugin-px2rem) 6 | [![NPM downloads](http://img.shields.io/npm/dm/postcss-plugin-px2rem.svg?style=flat)](https://npmjs.org/package/postcss-plugin-px2rem) 7 | [![Dependency Status](https://david-dm.org/ant-tool/postcss-plugin-px2rem.svg)](https://david-dm.org/ant-tool/postcss-plugin-px2rem) 8 | 9 | postcss plugin px2rem. 10 | 11 | 14 | 15 | ## Features 16 | 17 | A plugin for PostCSS that generates rem units from pixel units. 18 | 19 | ## Installation 20 | 21 | ```bash 22 | $ npm i --save postcss-plugin-px2rem 23 | ``` 24 | 25 | ## Usage 26 | 27 | ### input and output 28 | 29 | ```css 30 | // input 31 | h1 { 32 | margin: 0 0 20px; 33 | font-size: 32px; 34 | line-height: 1.2; 35 | letter-spacing: 1px; 36 | } 37 | 38 | // output 39 | h1 { 40 | margin: 0 0 0.2rem; 41 | font-size: 0.32rem; 42 | line-height: 1.2; 43 | letter-spacing: 0.01rem; 44 | } 45 | ``` 46 | 47 | ### original 48 | 49 | ```javascript 50 | import { writeFile, readFileSync } from 'fs'; 51 | import postcss from 'postcss'; 52 | import pxtorem from 'postcss-plugin-px2rem'; 53 | 54 | const css = readFileSync('/path/to/test.css', 'utf8'); 55 | const options = { 56 | replace: false, 57 | }; 58 | const processedCss = postcss(pxtorem(options)).process(css).css; 59 | 60 | writeFile('/path/to/test.rem.css', processedCss, err => { 61 | if (err) throw err; 62 | console.log('Rem file written.'); 63 | }); 64 | ``` 65 | 66 | ### with webpack 67 | 68 | ```javascript 69 | import px2rem from 'postcss-plugin-px2rem'; 70 | const px2remOpts = { 71 | ...... 72 | }; 73 | 74 | export default { 75 | module: { 76 | loaders: [ 77 | { 78 | test: /\.css$/, 79 | loader: 'style-loader!css-loader!postcss-loader', 80 | }, 81 | ], 82 | }, 83 | postcss: [px2rem(px2remOpts)], 84 | } 85 | ``` 86 | 87 | ### with [atool-build](https://github.com/ant-tool/atool-build) 88 | 89 | `webpack.connfig.js` 90 | 91 | ```javascript 92 | import webpack from 'atool-build/lib/webpack'; 93 | import px2rem from 'postcss-plugin-px2rem'; 94 | 95 | export default webpackConfig => { 96 | const px2remOpts = { 97 | ...... 98 | }; 99 | webpackConfig.postcss.push(px2rem(px2remOpts)); 100 | 101 | return webpackConfig; 102 | }; 103 | ``` 104 | 105 | ## Configuration 106 | 107 | Default: 108 | ```js 109 | { 110 | rootValue: 100, 111 | unitPrecision: 5, 112 | propWhiteList: [], 113 | propBlackList: [], 114 | exclude:false, 115 | selectorBlackList: [], 116 | ignoreIdentifier: false, 117 | replace: true, 118 | mediaQuery: false, 119 | minPixelValue: 0 120 | } 121 | ``` 122 | 123 | - `rootValue` (Number|Object) The root element font size. Default is 100. 124 | - If rootValue is an object, for example `{ px: 50, rpx: 100 }`, it will 125 | replace rpx to 1/100 rem , and px to 1/50 rem. 126 | - `unitPrecision` (Number) The decimal numbers to allow the REM units to grow to. 127 | - `propWhiteList` (Array) The properties that can change from px to rem. 128 | - Default is an empty array that means disable the white list and enable all properties. 129 | - Values need to be exact matches. 130 | - `propBlackList` (Array) The properties that should not change from px to rem. 131 | - Values need to be exact matches. 132 | - `exclude` (Reg) a way to exclude some folder,eg. /(node_module)/. 133 | - `selectorBlackList` (Array) The selectors to ignore and leave as px. 134 | - If value is string, it checks to see if selector contains the string. 135 | - `['body']` will match `.body-class` 136 | - If value is regexp, it checks to see if the selector matches the regexp. 137 | - `[/^body$/]` will match `body` but not `.body` 138 | - `ignoreIdentifier` (Boolean/String) a way to have a single property ignored, when ignoreIdentifier enabled, then `replace` would be set to `true` automatically. 139 | - `replace` (Boolean) replaces rules containing rems instead of adding fallbacks. 140 | - `mediaQuery` (Boolean) Allow px to be converted in media queries. 141 | - `minPixelValue` (Number) Set the minimum pixel value to replace. 142 | 143 | ### License 144 | MIT 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-plugin-px2rem", 3 | "version": "0.8.1", 4 | "description": "A plugin for PostCSS that generates rem units from multi units.", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "rm -rf lib && babel -d lib src", 8 | "compile:watch": "npm run compile -- --watch", 9 | "lint": "eslint src test", 10 | "prepublish": "npm run compile", 11 | "test": "babel-node $(npm bin)/babel-istanbul cover $(npm bin)/_mocha -- --no-timeouts", 12 | "coveralls": "cat ./coverage/lcov.info | coveralls" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ant-tool/postcss-plugin-px2rem.git" 17 | }, 18 | "keywords": [ 19 | "px", 20 | "rem", 21 | "postcss", 22 | "plugin" 23 | ], 24 | "author": "pigcan ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/ant-tool/postcss-plugin-px2rem/issues" 28 | }, 29 | "homepage": "https://github.com/ant-tool/postcss-plugin-px2rem#readme", 30 | "dependencies": { 31 | "postcss": "^5.0.21" 32 | }, 33 | "devDependencies": { 34 | "babel-cli": "^6.7.5", 35 | "babel-core": "^6.7.6", 36 | "babel-eslint": "^6.0.2", 37 | "babel-istanbul": "^0.7.0", 38 | "babel-plugin-add-module-exports": "^0.1.2", 39 | "babel-preset-es2015": "^6.6.0", 40 | "babel-preset-stage-0": "^6.5.0", 41 | "coveralls": "^2.11.9", 42 | "eslint": "^2.7.0", 43 | "eslint-config-airbnb": "^6.2.0", 44 | "expect": "^1.20.1", 45 | "mocha": "^2.4.5" 46 | }, 47 | "files": [ 48 | "lib", 49 | "index.js", 50 | "package.json", 51 | "README.md" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * respect to https://github.com/cuth/postcss-pxtorem/ 3 | **/ 4 | import postcss from 'postcss'; 5 | 6 | const defaultOpts = { 7 | rootValue: 100, 8 | unitPrecision: 5, 9 | selectorBlackList: [], 10 | propWhiteList: [], 11 | propBlackList: [], 12 | ignoreIdentifier: false, 13 | replace: true, 14 | mediaQuery: false, 15 | minPixelValue: 0, 16 | }; 17 | 18 | const toFixed = (number, precision) => { 19 | const multiplier = Math.pow(10, precision + 1); 20 | const wholeNumber = Math.floor(number * multiplier); 21 | 22 | return Math.round(wholeNumber / 10) * 10 / multiplier; 23 | }; 24 | const isObject = o => typeof o === 'object' && o !== null; 25 | 26 | const createPxReplace = (rootValue, identifier, unitPrecision, minPixelValue) => (m, $1, $2) => { 27 | if (!$1) return m; 28 | if (identifier && m.indexOf(identifier) === 0) return m.replace(identifier, ''); 29 | const pixels = parseFloat($1); 30 | if (pixels < minPixelValue) return m; 31 | // { px: 100, rpx: 50 } 32 | const baseValue = isObject(rootValue) ? rootValue[$2] : rootValue; 33 | const fixedVal = toFixed((pixels / baseValue), unitPrecision); 34 | 35 | return `${fixedVal}rem`; 36 | }; 37 | 38 | const declarationExists = (decls, prop, value) => decls.some(decl => 39 | decl.prop === prop && decl.value === value 40 | ); 41 | 42 | const blacklistedSelector = (blacklist, selector) => { 43 | if (typeof selector !== 'string') return false; 44 | 45 | return blacklist.some(regex => { 46 | if (typeof regex === 'string') return selector.indexOf(regex) !== -1; 47 | 48 | return selector.match(regex); 49 | }); 50 | }; 51 | 52 | const blacklistedProp = (blacklist, prop) => { 53 | if (typeof prop !== 'string') return false; 54 | 55 | return blacklist.some(regex => { 56 | if (typeof regex === 'string') return prop.indexOf(regex) !== -1; 57 | 58 | return prop.match(regex); 59 | }); 60 | }; 61 | 62 | const handleIgnoreIdentifierRegx = (identifier, unit) => { 63 | const _identifier = identifier; 64 | let backslashfy = _identifier.split('').join('\\'); 65 | backslashfy = `\\${backslashfy}`; 66 | const pattern = `"[^"]+"|'[^']+'|url\\([^\\)]+\\)|((?:${backslashfy}|\\d*)\\.?\\d+)(${unit})`; 67 | 68 | return new RegExp(pattern, 'ig'); 69 | }; 70 | 71 | export default postcss.plugin('postcss-plugin-px2rem', options => { 72 | const opts = { ...defaultOpts, ...options }; 73 | let unit = 'px'; 74 | if (isObject(opts.rootValue)) { 75 | unit = Object.keys(opts.rootValue).join('|'); 76 | } 77 | 78 | const regText = `"[^"]+"|'[^']+'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)(${unit})`; 79 | let pxRegex = new RegExp(regText, 'ig'); 80 | let identifier = opts.ignoreIdentifier; 81 | if (identifier && typeof identifier === 'string') { 82 | identifier = identifier.replace(/\s+/g, ''); 83 | opts.replace = true; 84 | pxRegex = handleIgnoreIdentifierRegx(identifier, unit); 85 | } else { 86 | identifier = false; 87 | } 88 | const pxReplace = createPxReplace(opts.rootValue, identifier, opts.unitPrecision, opts.minPixelValue); 89 | 90 | return css => { 91 | css.walkDecls((decl, i) => { 92 | const _decl = decl; 93 | // 1st check exclude 94 | if (opts.exclude && css.source.input.file && css.source.input.file.match(opts.exclude) !== null) return; 95 | // 2st check 'px' 96 | if (_decl.value.indexOf('px') === -1) return; 97 | // 3nd check property black list 98 | if (blacklistedProp(opts.propBlackList, _decl.prop)) return; 99 | // 4rd check property white list 100 | if (opts.propWhiteList.length && opts.propWhiteList.indexOf(_decl.prop) === -1) return; 101 | // 5th check seletor black list 102 | if (blacklistedSelector(opts.selectorBlackList, _decl.parent.selector)) return; 103 | 104 | const value = _decl.value.replace(pxRegex, pxReplace); 105 | 106 | // if rem unit already exists, do not add or replace 107 | if (declarationExists(_decl.parent, _decl.prop, value)) return; 108 | 109 | if (opts.replace) { 110 | _decl.value = value; 111 | } else { 112 | _decl.parent.insertAfter(i, _decl.clone({ 113 | value, 114 | })); 115 | } 116 | }); 117 | 118 | if (opts.mediaQuery) { 119 | css.walkAtRules('media', rule => { 120 | const _rule = rule; 121 | if (_rule.params.indexOf('px') === -1) return; 122 | _rule.params = _rule.params.replace(pxRegex, pxReplace); 123 | }); 124 | } 125 | }; 126 | }); 127 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import expect from 'expect'; 3 | import pxtorem from '../src/'; 4 | 5 | const basicCSS = '.rule { font-size: 15px }'; 6 | 7 | describe('px2rem', () => { 8 | it('should work on the readme example', () => { 9 | const input = 'h1 { margin: 0 0 20px 20px; font-size: 32px; line-height: 1.2; letter-spacing: 1px; }'; 10 | const output = 'h1 { margin: 0 0 0.2rem 0.2rem; font-size: 0.32rem; line-height: 1.2; letter-spacing: 0.01rem; }'; 11 | const processed = postcss(pxtorem()).process(input).css; 12 | 13 | expect(processed).toBe(output); 14 | }); 15 | 16 | it('should replace the px unit with rem', () => { 17 | const processed = postcss(pxtorem()).process(basicCSS).css; 18 | const expected = '.rule { font-size: 0.15rem }'; 19 | 20 | expect(processed).toBe(expected); 21 | }); 22 | 23 | it('should ignore non px properties', () => { 24 | const expected = '.rule { font-size: 2em }'; 25 | const processed = postcss(pxtorem()).process(expected).css; 26 | 27 | expect(processed).toBe(expected); 28 | }); 29 | 30 | it('should handle < 1 values and values without a leading 0', () => { 31 | const rules = '.rule { margin: 0.5rem .5px -0.2px -.2em }'; 32 | const expected = '.rule { margin: 0.5rem 0.005rem -0.002rem -.2em }'; 33 | const options = { 34 | propWhiteList: ['margin'], 35 | }; 36 | const processed = postcss(pxtorem(options)).process(rules).css; 37 | 38 | expect(processed).toBe(expected); 39 | }); 40 | 41 | it('should not add properties that already exist', () => { 42 | const expected = '.rule { font-size: 16px; font-size: 0.16rem; }'; 43 | const processed = postcss(pxtorem()).process(expected).css; 44 | 45 | expect(processed).toBe(expected); 46 | }); 47 | }); 48 | 49 | describe('value parsing', () => { 50 | it('should not replace values in double quotes or single quotes', () => { 51 | const options = { 52 | propWhiteList: [], 53 | }; 54 | const rules = '.rule { content: \'16px\'; font-family: "16px"; font-size: 16px; }'; 55 | const expected = '.rule { content: \'16px\'; font-family: "16px"; font-size: 0.16rem; }'; 56 | const processed = postcss(pxtorem(options)).process(rules).css; 57 | 58 | expect(processed).toBe(expected); 59 | }); 60 | 61 | it('should not replace values in `url()`', () => { 62 | const options = { 63 | propWhiteList: [], 64 | }; 65 | const rules = '.rule { background: url(16px.jpg); font-size: 16px; }'; 66 | const expected = '.rule { background: url(16px.jpg); font-size: 0.16rem; }'; 67 | const processed = postcss(pxtorem(options)).process(rules).css; 68 | 69 | expect(processed).toBe(expected); 70 | }); 71 | }); 72 | 73 | describe('rootValue', () => { 74 | it('should replace using a root value of 10', () => { 75 | const expected = '.rule { font-size: 1.5rem }'; 76 | const options = { 77 | rootValue: 10, 78 | }; 79 | const processed = postcss(pxtorem(options)).process(basicCSS).css; 80 | 81 | expect(processed).toBe(expected); 82 | }); 83 | }); 84 | 85 | describe('unitPrecision', () => { 86 | it('should replace using a decimal of 2 places', () => { 87 | const expected = '.rule { font-size: 0.94rem }'; 88 | const options = { 89 | rootValue: 16, 90 | unitPrecision: 2, 91 | }; 92 | const processed = postcss(pxtorem(options)).process(basicCSS).css; 93 | 94 | expect(processed).toBe(expected); 95 | }); 96 | }); 97 | 98 | describe('propWhiteList', () => { 99 | it('should only replace properties in the white list', () => { 100 | const expected = '.rule { font-size: 15px }'; 101 | const options = { 102 | propWhiteList: ['font'], 103 | }; 104 | const processed = postcss(pxtorem(options)).process(basicCSS).css; 105 | 106 | expect(processed).toBe(expected); 107 | }); 108 | 109 | it('should replace all properties when white list is empty', () => { 110 | const rules = '.rule { margin: 16px; font-size: 15px }'; 111 | const expected = '.rule { margin: 0.16rem; font-size: 0.15rem }'; 112 | const options = { 113 | propWhiteList: [], 114 | }; 115 | const processed = postcss(pxtorem(options)).process(rules).css; 116 | 117 | expect(processed).toBe(expected); 118 | }); 119 | }); 120 | 121 | describe('propBlackList', () => { 122 | it('should not replace properties in the black list', () => { 123 | const expected = '.rule { font-size: 15px }'; 124 | const options = { 125 | propBlackList: ['font'], 126 | }; 127 | const processed = postcss(pxtorem(options)).process(basicCSS).css; 128 | 129 | expect(processed).toBe(expected); 130 | }); 131 | }); 132 | 133 | describe('selectorBlackList', () => { 134 | it('should ignore selectors in the selector black list', () => { 135 | const rules = '.rule { font-size: 15px } .rule2 { font-size: 15px }'; 136 | const expected = '.rule { font-size: 0.15rem } .rule2 { font-size: 15px }'; 137 | const options = { 138 | selectorBlackList: ['.rule2'], 139 | }; 140 | const processed = postcss(pxtorem(options)).process(rules).css; 141 | 142 | expect(processed).toBe(expected); 143 | }); 144 | 145 | it('should ignore every selector with `body$`', () => { 146 | const rules = 'body { font-size: 16px; } .class-body$ { font-size: 16px; } .simple-class { font-size: 16px; }'; 147 | const expected = 'body { font-size: 0.16rem; } .class-body$ { font-size: 16px; } .simple-class { font-size: 0.16rem; }'; 148 | const options = { 149 | selectorBlackList: ['body$'], 150 | }; 151 | const processed = postcss(pxtorem(options)).process(rules).css; 152 | 153 | expect(processed).toBe(expected); 154 | }); 155 | 156 | it('should only ignore exactly `body`', () => { 157 | const rules = 'body { font-size: 16px; } .class-body { font-size: 16px; } .simple-class { font-size: 16px; }'; 158 | const expected = 'body { font-size: 16px; } .class-body { font-size: 0.16rem; } .simple-class { font-size: 0.16rem; }'; 159 | const options = { 160 | selectorBlackList: [/^body$/], 161 | }; 162 | const processed = postcss(pxtorem(options)).process(rules).css; 163 | 164 | expect(processed).toBe(expected); 165 | }); 166 | }); 167 | 168 | describe('ignoreIdentifier', () => { 169 | it('should not replace px when ignoreIdentifier enabled', () => { 170 | const options = { 171 | ignoreIdentifier: '00', 172 | }; 173 | const input = 'h1 { margin: 0 0 00.5px 16px; border-width: 001px; font-size: 32px; font-family: "16px"; }'; 174 | const output = 'h1 { margin: 0 0 .5px 0.16rem; border-width: 1px; font-size: 0.32rem; font-family: "16px"; }'; 175 | const processed = postcss(pxtorem(options)).process(input).css; 176 | 177 | expect(processed).toBe(output); 178 | }); 179 | }); 180 | 181 | describe('replace', () => { 182 | it('should leave fallback pixel unit with root em value', () => { 183 | const options = { 184 | replace: false, 185 | }; 186 | const processed = postcss(pxtorem(options)).process(basicCSS).css; 187 | const expected = '.rule { font-size: 15px; font-size: 0.15rem }'; 188 | 189 | expect(processed).toBe(expected); 190 | }); 191 | }); 192 | 193 | describe('mediaQuery', () => { 194 | it('should replace px in media queries', () => { 195 | const options = { 196 | mediaQuery: true, 197 | }; 198 | const processed = postcss(pxtorem(options)).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; 199 | const expected = '@media (min-width: 5rem) { .rule { font-size: 0.16rem } }'; 200 | 201 | expect(processed).toBe(expected); 202 | }); 203 | }); 204 | 205 | describe('minPixelValue', () => { 206 | it('should not replace values below minPixelValue', () => { 207 | const options = { 208 | propWhiteList: [], 209 | minPixelValue: 2, 210 | }; 211 | const rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; 212 | const expected = '.rule { border: 1px solid #000; font-size: 0.16rem; margin: 1px 0.1rem; }'; 213 | const processed = postcss(pxtorem(options)).process(rules).css; 214 | 215 | expect(processed).toBe(expected); 216 | }); 217 | }); 218 | 219 | describe('rpx support', function () { 220 | it('should work on the readme example', () => { 221 | const input = 'h1 { margin: 0 0 20rpx 20rpx; font-size: 32px; line-height: 1.2; letter-spacing: 1rpx; }'; 222 | const output = 'h1 { margin: 0 0 0.2rem 0.2rem; font-size: 0.64rem; line-height: 1.2; letter-spacing: 0.01rem; }'; 223 | const processed = postcss(pxtorem({ 224 | rootValue: { px: 50, rpx: 100 }, 225 | })).process(input).css; 226 | 227 | expect(processed).toBe(output); 228 | }); 229 | 230 | it('should replace rpx in media queries', () => { 231 | const options = { 232 | mediaQuery: true, 233 | rootValue: { px: 50, rpx: 100 }, 234 | }; 235 | const processed = postcss(pxtorem(options)).process('@media (min-width: 500rpx) { .rule { font-size: 16px } }').css; 236 | const expected = '@media (min-width: 5rem) { .rule { font-size: 0.32rem } }'; 237 | 238 | expect(processed).toBe(expected); 239 | }); 240 | 241 | it('should ignore selectors in the selector black list', () => { 242 | const rules = '.rule { font-size: 15rpx } .rule2 { font-size: 15px }'; 243 | const expected = '.rule { font-size: 0.15rem } .rule2 { font-size: 15px }'; 244 | const options = { 245 | selectorBlackList: ['.rule2'], 246 | rootValue: { px: 50, rpx: 100 }, 247 | }; 248 | const processed = postcss(pxtorem(options)).process(rules).css; 249 | 250 | expect(processed).toBe(expected); 251 | }); 252 | 253 | it('should not replace px when ignoreIdentifier enabled', () => { 254 | const options = { 255 | ignoreIdentifier: '00', 256 | rootValue: { px: 100, rpx: 100 }, 257 | }; 258 | const input = 'h1 { margin: 0 0 00.5px 16rpx; border-width: 001px; font-size: 32px; font-family: "16px"; }'; 259 | const output = 'h1 { margin: 0 0 .5px 0.16rem; border-width: 1px; font-size: 0.32rem; font-family: "16px"; }'; 260 | const processed = postcss(pxtorem(options)).process(input).css; 261 | 262 | expect(processed).toBe(output); 263 | }); 264 | }); 265 | 266 | describe('exclude support', () => { 267 | it('should work on the readme example', () => { 268 | const input = 'h1 { margin: 0 0 20px 20px; font-size: 32px; line-height: 1.2; letter-spacing: 1px; }'; 269 | const output = 'h1 { margin: 0 0 20px 20px; font-size: 32px; line-height: 1.2; letter-spacing: 1px; }'; 270 | const processed = postcss(pxtorem({ 271 | exclude: /(node_modules)/, 272 | })).process(input, { 273 | from: 'node_modules/third.css', 274 | }).css; 275 | expect(processed).toBe(output); 276 | }); 277 | 278 | it('should work when exclude option range doesn\'t cover', () => { 279 | const input = 'h1 { margin: 0 0 20px 20px; font-size: 32px; line-height: 1.2; letter-spacing: 1px; }'; 280 | const output = 'h1 { margin: 0 0 0.2rem 0.2rem; font-size: 0.32rem; line-height: 1.2; letter-spacing: 0.01rem; }'; 281 | const processed = postcss(pxtorem({ 282 | exclude: /(node_modules)/, 283 | })).process(input, { 284 | from: 'lib/own.css', 285 | }).css; 286 | expect(processed).toBe(output); 287 | }); 288 | }); 289 | --------------------------------------------------------------------------------