From 26500c5d5f5b5043bf2f409dd1216ea07ff60735 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Fri, 8 Jun 2018 14:46:44 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=A2=9E=E5=8A=A0px=E8=BD=ACrem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 2 +- index.js | 2 +- src/index.js => lib/dpxtopx.js | 38 +++-- lib/index.js | 10 ++ lib/pxtorem.js | 122 +++++++++++++++ package.json | 3 +- test/index-test.js | 264 +++++++++++++++++++++++++++++++++ test/test.js | 16 +- 8 files changed, 429 insertions(+), 28 deletions(-) rename src/index.js => lib/dpxtopx.js (54%) create mode 100644 lib/index.js create mode 100644 lib/pxtorem.js create mode 100755 test/index-test.js diff --git a/README.md b/README.md index f1aca77..387e348 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## 功能 -一个postcss插件,用来将css中的dpx 换算成px +基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem ### 输入输出 diff --git a/index.js b/index.js index b9bf89d..1ee7467 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,2 @@ -module.exports = require('./src'); +module.exports = require('./lib'); diff --git a/src/index.js b/lib/dpxtopx.js similarity index 54% rename from src/index.js rename to lib/dpxtopx.js index c02b1e8..ed5f751 100644 --- a/src/index.js +++ b/lib/dpxtopx.js @@ -1,31 +1,29 @@ var postcss = require('postcss'); const defaultOpts = { - prevName: 'data-dpr', + prefix: 'data-dpr', maxDpr: 3, delete: true }; -module.exports = postcss.plugin('postcss-plugin-dpxtopx', function(options) { - return function(root) { - options = Object.assign({}, defaultOpts, options || {}); - let insertRule = insertDpx(options.prevName, options.maxDpr); - root.walkRules((rule) => { - let declList = []; - rule.walkDecls((decl) => { - let data = getDpx(decl); - if (data) { - declList.push(data); - if (options.delete) { - decl.remove(); - } +module.exports = function(opts, root) { + let options = { ...defaultOpts, ...opts }; + let insertRule = insertDpx(options.prefix, options.maxDpr); + root.walkRules((rule) => { + let declList = []; + rule.walkDecls((decl) => { + let data = getDpx(decl); + if (data) { + declList.push(data); + if (options.delete) { + decl.remove(); } - }) - insertRule(rule, declList); + } }) - } -}); + insertRule(rule, declList); + }) +}; const getDpx = (decl) => { @@ -44,13 +42,13 @@ const getDpx = (decl) => { return undefined; } -const insertDpx = (prevName, maxDpr) => (rule, declList) => { +const insertDpx = (prefix, maxDpr) => (rule, declList) => { if (!declList || declList.length === 0) { return; } let unit = 'px'; for (let i = maxDpr; i > 0; i--) { - let ruleName = `[${prevName}="${i}"] ${rule.selector}`; + let ruleName = `[${prefix}="${i}"] ${rule.selector}`; let newRule = createRule(ruleName); declList.forEach(decl => { newRule.append({ prop: decl.prop, value: (decl.value * i) + unit }); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..7a03c17 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,10 @@ +let postcss = require('postcss'); +let pxtorem = require('./pxtorem'); +let dpxtopx = require('./dpxtopx'); +module.exports = postcss.plugin('postcss-plugin-pxtorem', options => { + const opts = { ...options }; + return css => { + dpxtopx(opts, css); + pxtorem(opts, css); + }; +}); diff --git a/lib/pxtorem.js b/lib/pxtorem.js new file mode 100644 index 0000000..0e53bd8 --- /dev/null +++ b/lib/pxtorem.js @@ -0,0 +1,122 @@ +let postcss = require('postcss'); +const defaultOpts = { + rootValue: 100, + unitPrecision: 5, + selectorBlackList: [], + propWhiteList: [], + propBlackList: [], + ignoreIdentifier: false, + replace: true, + mediaQuery: false, + minPixelValue: 0, + pxtorem: true +}; + +const toFixed = (number, precision) => { + const multiplier = Math.pow(10, precision + 1); + const wholeNumber = Math.floor(number * multiplier); + + return Math.round(wholeNumber / 10) * 10 / multiplier; +}; +const isObject = o => typeof o === 'object' && o !== null; + +const createPxReplace = (rootValue, identifier, unitPrecision, minPixelValue) => (m, $1, $2) => { + if (!$1) return m; + if (identifier && m.indexOf(identifier) === 0) return m.replace(identifier, ''); + const pixels = parseFloat($1); + if (pixels < minPixelValue) return m; + // { px: 100, rpx: 50 } + const baseValue = isObject(rootValue) ? rootValue[$2] : rootValue; + const fixedVal = toFixed((pixels / baseValue), unitPrecision); + + return `${fixedVal}rem`; +}; + +const declarationExists = (decls, prop, value) => decls.some(decl => + decl.prop === prop && decl.value === value +); + +const blacklistedSelector = (blacklist, selector) => { + if (typeof selector !== 'string') return false; + + return blacklist.some(regex => { + if (typeof regex === 'string') return selector.indexOf(regex) !== -1; + + return selector.match(regex); + }); +}; + +const blacklistedProp = (blacklist, prop) => { + if (typeof prop !== 'string') return false; + + return blacklist.some(regex => { + if (typeof regex === 'string') return prop.indexOf(regex) !== -1; + + return prop.match(regex); + }); +}; + +const handleIgnoreIdentifierRegx = (identifier, unit) => { + const _identifier = identifier; + let backslashfy = _identifier.split('').join('\\'); + backslashfy = `\\${backslashfy}`; + const pattern = `"[^"]+"|'[^']+'|url\\([^\\)]+\\)|((?:${backslashfy}|\\d*)\\.?\\d+)(${unit})`; + + return new RegExp(pattern, 'ig'); +}; + +module.exports = (options, css) => { + const opts = { ...defaultOpts, ...options }; + if (!opts.pxtorem) { + return; + } + let unit = 'px'; + if (isObject(opts.rootValue)) { + unit = Object.keys(opts.rootValue).join('|'); + } + + const regText = `"[^"]+"|'[^']+'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)(${unit})`; + let pxRegex = new RegExp(regText, 'ig'); + let identifier = opts.ignoreIdentifier; + if (identifier && typeof identifier === 'string') { + identifier = identifier.replace(/\s+/g, ''); + opts.replace = true; + pxRegex = handleIgnoreIdentifierRegx(identifier, unit); + } else { + identifier = false; + } + const pxReplace = createPxReplace(opts.rootValue, identifier, opts.unitPrecision, opts.minPixelValue); + + css.walkDecls((decl, i) => { + const _decl = decl; + // 1st check 'px' + if (_decl.value.indexOf('px') === -1) return; + // 2nd check property black list + if (blacklistedProp(opts.propBlackList, _decl.prop)) return; + // 3rd check property white list + if (opts.propWhiteList.length && opts.propWhiteList.indexOf(_decl.prop) === -1) return; + // 4th check seletor black list + if (blacklistedSelector(opts.selectorBlackList, _decl.parent.selector)) return; + + const value = _decl.value.replace(pxRegex, pxReplace); + + // if rem unit already exists, do not add or replace + if (declarationExists(_decl.parent, _decl.prop, value)) return; + + if (opts.replace) { + _decl.value = value; + } else { + _decl.parent.insertAfter(i, _decl.clone({ + value, + })); + } + }); + + if (opts.mediaQuery) { + css.walkAtRules('media', rule => { + const _rule = rule; + if (_rule.params.indexOf('px') === -1) return; + _rule.params = _rule.params.replace(pxRegex, pxReplace); + }); + } +}; diff --git a/package.json b/package.json index 9094037..3d237ff 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "基于 postcss 添加 dpx 转 px 单位支持", "main": "index.js", "scripts": { - "test": "mocha" + "test": "mocha", + "test-rem": "mocha ./test/index-test.js" }, "repository": { "type": "git", diff --git a/test/index-test.js b/test/index-test.js new file mode 100755 index 0000000..1fca9b5 --- /dev/null +++ b/test/index-test.js @@ -0,0 +1,264 @@ +let postcss = require('postcss'); +let expect = require('expect'); +let pxtorem = require('../lib/'); + +const basicCSS = '.rule { font-size: 15px }'; + +describe('px2rem', () => { + it('should work on the readme example', () => { + const input = 'h1 { margin: 0 0 20px 20px; font-size: 32px; line-height: 1.2; letter-spacing: 1px; }'; + const output = 'h1 { margin: 0 0 0.2rem 0.2rem; font-size: 0.32rem; line-height: 1.2; letter-spacing: 0.01rem; }'; + const processed = postcss(pxtorem()).process(input).css; + + expect(processed).toBe(output); + }); + + it('should replace the px unit with rem', () => { + const processed = postcss(pxtorem()).process(basicCSS).css; + const expected = '.rule { font-size: 0.15rem }'; + + expect(processed).toBe(expected); + }); + + it('should ignore non px properties', () => { + const expected = '.rule { font-size: 2em }'; + const processed = postcss(pxtorem()).process(expected).css; + + expect(processed).toBe(expected); + }); + + it('should handle < 1 values and values without a leading 0', () => { + const rules = '.rule { margin: 0.5rem .5px -0.2px -.2em }'; + const expected = '.rule { margin: 0.5rem 0.005rem -0.002rem -.2em }'; + const options = { + propWhiteList: ['margin'], + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); + + it('should not add properties that already exist', () => { + const expected = '.rule { font-size: 16px; font-size: 0.16rem; }'; + const processed = postcss(pxtorem()).process(expected).css; + + expect(processed).toBe(expected); + }); +}); + +describe('value parsing', () => { + it('should not replace values in double quotes or single quotes', () => { + const options = { + propWhiteList: [], + }; + const rules = '.rule { content: \'16px\'; font-family: "16px"; font-size: 16px; }'; + const expected = '.rule { content: \'16px\'; font-family: "16px"; font-size: 0.16rem; }'; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); + + it('should not replace values in `url()`', () => { + const options = { + propWhiteList: [], + }; + const rules = '.rule { background: url(16px.jpg); font-size: 16px; }'; + const expected = '.rule { background: url(16px.jpg); font-size: 0.16rem; }'; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); +}); + +describe('rootValue', () => { + it('should replace using a root value of 10', () => { + const expected = '.rule { font-size: 1.5rem }'; + const options = { + rootValue: 10, + }; + const processed = postcss(pxtorem(options)).process(basicCSS).css; + + expect(processed).toBe(expected); + }); +}); + +describe('unitPrecision', () => { + it('should replace using a decimal of 2 places', () => { + const expected = '.rule { font-size: 0.94rem }'; + const options = { + rootValue: 16, + unitPrecision: 2, + }; + const processed = postcss(pxtorem(options)).process(basicCSS).css; + + expect(processed).toBe(expected); + }); +}); + +describe('propWhiteList', () => { + it('should only replace properties in the white list', () => { + const expected = '.rule { font-size: 15px }'; + const options = { + propWhiteList: ['font'], + }; + const processed = postcss(pxtorem(options)).process(basicCSS).css; + + expect(processed).toBe(expected); + }); + + it('should replace all properties when white list is empty', () => { + const rules = '.rule { margin: 16px; font-size: 15px }'; + const expected = '.rule { margin: 0.16rem; font-size: 0.15rem }'; + const options = { + propWhiteList: [], + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); +}); + +describe('propBlackList', () => { + it('should not replace properties in the black list', () => { + const expected = '.rule { font-size: 15px }'; + const options = { + propBlackList: ['font'], + }; + const processed = postcss(pxtorem(options)).process(basicCSS).css; + + expect(processed).toBe(expected); + }); +}); + +describe('selectorBlackList', () => { + it('should ignore selectors in the selector black list', () => { + const rules = '.rule { font-size: 15px } .rule2 { font-size: 15px }'; + const expected = '.rule { font-size: 0.15rem } .rule2 { font-size: 15px }'; + const options = { + selectorBlackList: ['.rule2'], + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); + + it('should ignore every selector with `body$`', () => { + const rules = 'body { font-size: 16px; } .class-body$ { font-size: 16px; } .simple-class { font-size: 16px; }'; + const expected = 'body { font-size: 0.16rem; } .class-body$ { font-size: 16px; } .simple-class { font-size: 0.16rem; }'; + const options = { + selectorBlackList: ['body$'], + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); + + it('should only ignore exactly `body`', () => { + const rules = 'body { font-size: 16px; } .class-body { font-size: 16px; } .simple-class { font-size: 16px; }'; + const expected = 'body { font-size: 16px; } .class-body { font-size: 0.16rem; } .simple-class { font-size: 0.16rem; }'; + const options = { + selectorBlackList: [/^body$/], + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); +}); + +describe('ignoreIdentifier', () => { + it('should not replace px when ignoreIdentifier enabled', () => { + const options = { + ignoreIdentifier: '00', + }; + const input = 'h1 { margin: 0 0 00.5px 16px; border-width: 001px; font-size: 32px; font-family: "16px"; }'; + const output = 'h1 { margin: 0 0 .5px 0.16rem; border-width: 1px; font-size: 0.32rem; font-family: "16px"; }'; + const processed = postcss(pxtorem(options)).process(input).css; + + expect(processed).toBe(output); + }); +}); + +describe('replace', () => { + it('should leave fallback pixel unit with root em value', () => { + const options = { + replace: false, + }; + const processed = postcss(pxtorem(options)).process(basicCSS).css; + const expected = '.rule { font-size: 15px; font-size: 0.15rem }'; + + expect(processed).toBe(expected); + }); +}); + +describe('mediaQuery', () => { + it('should replace px in media queries', () => { + const options = { + mediaQuery: true, + }; + const processed = postcss(pxtorem(options)).process('@media (min-width: 500px) { .rule { font-size: 16px } }').css; + const expected = '@media (min-width: 5rem) { .rule { font-size: 0.16rem } }'; + + expect(processed).toBe(expected); + }); +}); + +describe('minPixelValue', () => { + it('should not replace values below minPixelValue', () => { + const options = { + propWhiteList: [], + minPixelValue: 2, + }; + const rules = '.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }'; + const expected = '.rule { border: 1px solid #000; font-size: 0.16rem; margin: 1px 0.1rem; }'; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); +}); + +describe('rpx support', function() { + it('should work on the readme example', () => { + const input = 'h1 { margin: 0 0 20rpx 20rpx; font-size: 32px; line-height: 1.2; letter-spacing: 1rpx; }'; + const output = 'h1 { margin: 0 0 0.2rem 0.2rem; font-size: 0.64rem; line-height: 1.2; letter-spacing: 0.01rem; }'; + const processed = postcss(pxtorem({ + rootValue: { px: 50, rpx: 100 }, + })).process(input).css; + + expect(processed).toBe(output); + }); + + it('should replace rpx in media queries', () => { + const options = { + mediaQuery: true, + rootValue: { px: 50, rpx: 100 }, + }; + const processed = postcss(pxtorem(options)).process('@media (min-width: 500rpx) { .rule { font-size: 16px } }').css; + const expected = '@media (min-width: 5rem) { .rule { font-size: 0.32rem } }'; + + expect(processed).toBe(expected); + }); + + it('should ignore selectors in the selector black list', () => { + const rules = '.rule { font-size: 15rpx } .rule2 { font-size: 15px }'; + const expected = '.rule { font-size: 0.15rem } .rule2 { font-size: 15px }'; + const options = { + selectorBlackList: ['.rule2'], + rootValue: { px: 50, rpx: 100 }, + }; + const processed = postcss(pxtorem(options)).process(rules).css; + + expect(processed).toBe(expected); + }); + + it('should not replace px when ignoreIdentifier enabled', () => { + const options = { + ignoreIdentifier: '00', + rootValue: { px: 100, rpx: 100 }, + }; + const input = 'h1 { margin: 0 0 00.5px 16rpx; border-width: 001px; font-size: 32px; font-family: "16px"; }'; + const output = 'h1 { margin: 0 0 .5px 0.16rem; border-width: 1px; font-size: 0.32rem; font-family: "16px"; }'; + const processed = postcss(pxtorem(options)).process(input).css; + + expect(processed).toBe(output); + }); +}); diff --git a/test/test.js b/test/test.js index b7efa1e..126d5e8 100644 --- a/test/test.js +++ b/test/test.js @@ -6,6 +6,9 @@ const basicCSS = '.rule { font-size: 10dpx }'; describe('dpxtopx', () => { it('测试基础转化dpx为px', () => { + const options = { + pxtorem: false + }; const expected = `.rule { } [data-dpr="1"] .rule { @@ -17,17 +20,18 @@ describe('dpxtopx', () => { [data-dpr="3"] .rule { font-size: 30px }`; - const processed = postcss(dpxtopx()).process(basicCSS).css; + const processed = postcss(dpxtopx(options)).process(basicCSS).css; expect(processed).toBe(expected); }); }); -describe('prevName', () => { +describe('prefix', () => { it('测试prevName', () => { const options = { - prevName: "test" + prefix: "test", + pxtorem: false }; const expected = `.rule { } @@ -50,7 +54,8 @@ describe('maxDpr', () => { it('测试最大dpr', () => { const options = { - maxDpr: 4 + maxDpr: 4, + pxtorem: false }; const expected = `.rule { } @@ -73,7 +78,8 @@ describe('maxDpr', () => { describe('delete', () => { const options = { - delete: false + delete: false, + pxtorem: false }; it('测试delete', () => { From a87aed9b7359c0f12113fca3b19cbd2b203e6aa0 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Fri, 8 Jun 2018 15:43:12 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E7=BC=96=E5=86=99readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++---------- lib/dpxtopx.js | 2 ++ test/test.js | 2 +- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 387e348..cbc5697 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ## 功能 -基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem +基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem. +保留了postcss-plugin-px2rem的所有配置。 ### 输入输出 @@ -12,7 +13,7 @@ h1 { // 输出 h1 { - + font-size: 16px } [data-dpr="1"] h1 { font-size: 16px @@ -23,11 +24,26 @@ h1 { [data-dpr="3"] h1 { font-size: 48px } +// 输入 +h1 { + margin: 0 0 20px; + font-size: 32px; + line-height: 1.2; + letter-spacing: 1px; +} + +// 输出 +h1 { + margin: 0 0 0.2rem; + font-size: 0.32rem; + line-height: 1.2; + letter-spacing: 0.01rem; +} ``` ### webpack配置 -webpack rules添加 postcss-loader +webpack rules添加 postcss-loader配置 ```js module.exports = { module: { @@ -40,33 +56,62 @@ module.exports = { } ``` -在根目录新增 `postcss.config.js` 文件: +在根目录新增 `postcss.config.js` postcss配置文件: ```js +// 默认的配置 +const pxtoremOpts = { + ...... +}; module.exports = { plugins: [ - require('postcss-plugin-dpxtopx')({ - // prevName: 'data-dpr' - // maxDpr: 3, - // delete: true - }) + require('postcss-plugin-pxtorem')(pxtoremOpts) ] } ``` ## 配置 -Default: +默认配置: ```js { - prevName: 'data-dpr', + rootValue: 100, + unitPrecision: 5, + propWhiteList: [], + propBlackList: [], + selectorBlackList: [], + ignoreIdentifier: false, + replace: true, + mediaQuery: false, + minPixelValue: 0 + prefix: 'data-dpr', maxDpr: 3, - delete: true + delete: true, + pxtorem: true } ``` -`prevName` 生成的前缀 默认值 data-dpr +`prefix` 生成的前缀 默认值 data-dpr `maxDpr` 生成的dpr的最大值 默认值 3 `delete` 是否删除匹配到的声明 默认值 true - +`pxtorem` 是否需要讲px转化成rem 默认值 true +- `rootValue` (Number|Object) The root element font size. Default is 100. + - If rootValue is an object, for example `{ px: 50, rpx: 100 }`, it will + replace rpx to 1/100 rem , and px to 1/50 rem. +- `unitPrecision` (Number) The decimal numbers to allow the REM units to grow to. +- `propWhiteList` (Array) The properties that can change from px to rem. + - Default is an empty array that means disable the white list and enable all properties. + - Values need to be exact matches. +- `propBlackList` (Array) The properties that should not change from px to rem. + - Values need to be exact matches. +- `selectorBlackList` (Array) The selectors to ignore and leave as px. + - If value is string, it checks to see if selector contains the string. + - `['body']` will match `.body-class` + - If value is regexp, it checks to see if the selector matches the regexp. + - `[/^body$/]` will match `body` but not `.body` +- `ignoreIdentifier` (Boolean/String) a way to have a single property ignored, when ignoreIdentifier enabled, then `replace` would be set to `true` automatically. +- `replace` (Boolean) replaces rules containing rems instead of adding fallbacks. +- `mediaQuery` (Boolean) Allow px to be converted in media queries. +- `minPixelValue` (Number) Set the minimum pixel value to replace. ## 测试 -控制台输入 npm test \ No newline at end of file +控制台输入 npm run test 测试dpx转px +控制台输入 npm run test-rem 测试px转rem \ No newline at end of file diff --git a/lib/dpxtopx.js b/lib/dpxtopx.js index ed5f751..0410636 100644 --- a/lib/dpxtopx.js +++ b/lib/dpxtopx.js @@ -18,6 +18,8 @@ module.exports = function(opts, root) { declList.push(data); if (options.delete) { decl.remove(); + } else { + decl.value = data.value + 'px'; } } }) diff --git a/test/test.js b/test/test.js index 126d5e8..0fe7d24 100644 --- a/test/test.js +++ b/test/test.js @@ -84,7 +84,7 @@ describe('delete', () => { it('测试delete', () => { const expected = - `.rule { font-size: 10dpx } + `.rule { font-size: 10px } [data-dpr="1"] .rule { font-size: 10px } [data-dpr="2"] .rule { font-size: 20px } [data-dpr="3"] .rule { font-size: 30px }`; From c96ab6bb75c3ad45c594b062558fd564a7807cb5 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Fri, 8 Jun 2018 16:00:15 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cbc5697..fbc6cf5 100644 --- a/README.md +++ b/README.md @@ -94,24 +94,19 @@ module.exports = { `maxDpr` 生成的dpr的最大值 默认值 3 `delete` 是否删除匹配到的声明 默认值 true `pxtorem` 是否需要讲px转化成rem 默认值 true -- `rootValue` (Number|Object) The root element font size. Default is 100. - - If rootValue is an object, for example `{ px: 50, rpx: 100 }`, it will - replace rpx to 1/100 rem , and px to 1/50 rem. -- `unitPrecision` (Number) The decimal numbers to allow the REM units to grow to. -- `propWhiteList` (Array) The properties that can change from px to rem. - - Default is an empty array that means disable the white list and enable all properties. - - Values need to be exact matches. -- `propBlackList` (Array) The properties that should not change from px to rem. - - Values need to be exact matches. -- `selectorBlackList` (Array) The selectors to ignore and leave as px. - - If value is string, it checks to see if selector contains the string. - - `['body']` will match `.body-class` - - If value is regexp, it checks to see if the selector matches the regexp. - - `[/^body$/]` will match `body` but not `.body` -- `ignoreIdentifier` (Boolean/String) a way to have a single property ignored, when ignoreIdentifier enabled, then `replace` would be set to `true` automatically. -- `replace` (Boolean) replaces rules containing rems instead of adding fallbacks. -- `mediaQuery` (Boolean) Allow px to be converted in media queries. -- `minPixelValue` (Number) Set the minimum pixel value to replace. +`rootValue` (Number\Object)根元素字体大小。默认值为100。-如果rootValue是一个对象,例如`{px:50,rpx:100}‘,它将替换RPX到1/100 rem,将px替换为1/50 rem. + +`unitPrecision` (Number)十进制数,允许REM单元保留几位小数 +`propWhiteList` (Array)可以从px更改为rem的属性。-默认值是一个空数组,它意味着禁用白名单并启用所有属性。-值必须是完全匹配的。 +`propBlackList` (Array)不应从px更改为rem的属性。-值需要精确匹配. + +`selectorBlackList` (Array) 如果值是字符串,则检查选择器是否包含字符串。-`[body]`将匹配`.body-class`-如果值是regexp,则检查选择器是否与regexp匹配。`[/^body$/]`将匹配`body`,但不匹配`.body`。 + + +`ignoreIdentifier` (Boolean/String) 一种将单个属性忽略的方法,如果启用了不命名标识符,则“replace”将自动设置为“true”。 +`replace` (Boolean) 取代包含rems的规则,而不是添加后备项。 +`mediaQuery` (Boolean) 允许在媒体查询中转换PX。 +`minPixelValue` (Number) 设置要替换的最小像素值。 ## 测试 控制台输入 npm run test 测试dpx转px 控制台输入 npm run test-rem 测试px转rem \ No newline at end of file From 77194dd83b6605afe4ff8797ee34b8024c24ba39 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Mon, 11 Jun 2018 10:05:39 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 2 +- package.json | 8 ++++---- test/test.js | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fbc6cf5..1300442 100644 --- a/README.md +++ b/README.md @@ -108,5 +108,5 @@ module.exports = { `mediaQuery` (Boolean) 允许在媒体查询中转换PX。 `minPixelValue` (Number) 设置要替换的最小像素值。 ## 测试 -控制台输入 npm run test 测试dpx转px +控制台输入 npm run test 测试dpx转px以及px转rem 控制台输入 npm run test-rem 测试px转rem \ No newline at end of file diff --git a/package.json b/package.json index 3d237ff..4c22b8d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "postcss-plugin-dpxtopx", - "version": "1.0.0", - "description": "基于 postcss 添加 dpx 转 px 单位支持", + "name": "postcss-plugin-pxtorem", + "version": "0.0.1", + "description": "基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem.保留了postcss-plugin-px2rem的所有配置。", "main": "index.js", "scripts": { "test": "mocha", @@ -17,7 +17,7 @@ "px" ], "author": "", - "license": "ISC", + "license": "MIT", "bugs": { "url": "https://github.com/sunxhh/postcss-plugin-pxtorem/issues" }, diff --git a/test/test.js b/test/test.js index 0fe7d24..1892f7e 100644 --- a/test/test.js +++ b/test/test.js @@ -2,7 +2,7 @@ const postcss = require('postcss'); const expect = require('expect'); const dpxtopx = require('../index.js'); -const basicCSS = '.rule { font-size: 10dpx }'; +const basicDpxCSS = '.rule { font-size: 10dpx }'; describe('dpxtopx', () => { it('测试基础转化dpx为px', () => { @@ -20,7 +20,7 @@ describe('dpxtopx', () => { [data-dpr="3"] .rule { font-size: 30px }`; - const processed = postcss(dpxtopx(options)).process(basicCSS).css; + const processed = postcss(dpxtopx(options)).process(basicDpxCSS).css; expect(processed).toBe(expected); }); @@ -44,7 +44,7 @@ describe('prefix', () => { [test="3"] .rule { font-size: 30px }`; - const processed = postcss(dpxtopx(options)).process(basicCSS).css; + const processed = postcss(dpxtopx(options)).process(basicDpxCSS).css; expect(processed).toBe(expected); }); @@ -71,7 +71,7 @@ describe('maxDpr', () => { [data-dpr="4"] .rule { font-size: 40px }`; - const processed = postcss(dpxtopx(options)).process(basicCSS).css; + const processed = postcss(dpxtopx(options)).process(basicDpxCSS).css; expect(processed).toBe(expected); }); }); @@ -88,7 +88,7 @@ describe('delete', () => { [data-dpr="1"] .rule { font-size: 10px } [data-dpr="2"] .rule { font-size: 20px } [data-dpr="3"] .rule { font-size: 30px }`; - const processed = postcss(dpxtopx(options)).process(basicCSS).css; + const processed = postcss(dpxtopx(options)).process(basicDpxCSS).css; expect(processed).toBe(expected); }); }); \ No newline at end of file From f7b81c752c805ef3294e712943247129133f5880 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Mon, 11 Jun 2018 11:37:29 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 27 ++++++++++++++------------- package.json | 11 ++++++++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1300442..cf45527 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ## 功能 -基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem. -保留了postcss-plugin-px2rem的所有配置。 +基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem.保留了postcss-plugin-px2rem的所有配置。 ### 输入输出 @@ -40,9 +39,11 @@ h1 { letter-spacing: 0.01rem; } ``` +### 安装 +npm install --save-dev postcss-plugin-pxtorem - -### webpack配置 +### 配置 +###### webpack配置 webpack rules添加 postcss-loader配置 ```js module.exports = { @@ -55,7 +56,7 @@ module.exports = { } } ``` - +##### postcss 配置 在根目录新增 `postcss.config.js` postcss配置文件: ```js @@ -90,17 +91,17 @@ module.exports = { pxtorem: true } ``` -`prefix` 生成的前缀 默认值 data-dpr -`maxDpr` 生成的dpr的最大值 默认值 3 -`delete` 是否删除匹配到的声明 默认值 true -`pxtorem` 是否需要讲px转化成rem 默认值 true +`prefix` (String)生成的前缀 默认值 data-dpr +`maxDpr` (Number)生成的dpr的最大值 默认值 3 +`delete` (Boolean)是否删除匹配到的声明 默认值 true +`pxtorem` (Boolean)是否需要讲px转化成rem 默认值 true `rootValue` (Number\Object)根元素字体大小。默认值为100。-如果rootValue是一个对象,例如`{px:50,rpx:100}‘,它将替换RPX到1/100 rem,将px替换为1/50 rem. -`unitPrecision` (Number)十进制数,允许REM单元保留几位小数 -`propWhiteList` (Array)可以从px更改为rem的属性。-默认值是一个空数组,它意味着禁用白名单并启用所有属性。-值必须是完全匹配的。 -`propBlackList` (Array)不应从px更改为rem的属性。-值需要精确匹配. +`unitPrecision` (Number)十进制数,允许rem保留几位小数 +`propWhiteList` (Array)可以从px更改为rem的属性。默认值是一个空数组,白名单并启用所有属性。值必须是完全匹配的。 +`propBlackList` (Array)不应从px更改为rem的属性。值需要完全匹配. -`selectorBlackList` (Array) 如果值是字符串,则检查选择器是否包含字符串。-`[body]`将匹配`.body-class`-如果值是regexp,则检查选择器是否与regexp匹配。`[/^body$/]`将匹配`body`,但不匹配`.body`。 +`selectorBlackList` (Array) 如果值是字符串,则检查选择器是否包含字符串,如`[body]`将匹配`.body-class`。如果值是正则表达式,则检查选择器是否与该正则表达式匹配,如`[/^body$/]`将匹配`body`,但不匹配`.body`。 `ignoreIdentifier` (Boolean/String) 一种将单个属性忽略的方法,如果启用了不命名标识符,则“replace”将自动设置为“true”。 diff --git a/package.json b/package.json index 4c22b8d..90021fc 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,17 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/sunxhh/postcss-plugin-pxtorem.git" + "url": "git@github.com:sunxhh/postcss-plugin-pxtorem.git" }, "keywords": [ "postcss", "dpx", "px" ], - "author": "", + "author": { + "name": "sunxh", + "email": "997854244@qq.com" + }, "license": "MIT", "bugs": { "url": "https://github.com/sunxhh/postcss-plugin-pxtorem/issues" @@ -30,5 +33,7 @@ "expect": "^23.0.0", "mocha": "^5.2.0" }, - "dependencies": {} + "dependencies": { + "postcss-plugin-pxtorem": "0.0.1" + } } From b75c00c722cba0be890668fe1152edef31d4efe3 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Tue, 12 Jun 2018 18:04:59 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 5 +- "\345\256\236\347\216\260.md" | 120 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 "\345\256\236\347\216\260.md" diff --git a/README.md b/README.md index cf45527..93c5291 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ module.exports = { `pxtorem` (Boolean)是否需要讲px转化成rem 默认值 true `rootValue` (Number\Object)根元素字体大小。默认值为100。-如果rootValue是一个对象,例如`{px:50,rpx:100}‘,它将替换RPX到1/100 rem,将px替换为1/50 rem. -`unitPrecision` (Number)十进制数,允许rem保留几位小数 +`unitPrecision` (Number)允许rem保留几位小数 `propWhiteList` (Array)可以从px更改为rem的属性。默认值是一个空数组,白名单并启用所有属性。值必须是完全匹配的。 `propBlackList` (Array)不应从px更改为rem的属性。值需要完全匹配. @@ -109,5 +109,4 @@ module.exports = { `mediaQuery` (Boolean) 允许在媒体查询中转换PX。 `minPixelValue` (Number) 设置要替换的最小像素值。 ## 测试 -控制台输入 npm run test 测试dpx转px以及px转rem -控制台输入 npm run test-rem 测试px转rem \ No newline at end of file +控制台输入 npm run test 测试dpx转px以及px转rem \ No newline at end of file diff --git "a/\345\256\236\347\216\260.md" "b/\345\256\236\347\216\260.md" new file mode 100644 index 0000000..2d1f1a2 --- /dev/null +++ "b/\345\256\236\347\216\260.md" @@ -0,0 +1,120 @@ +### postcss +PostCSS 本身是一个功能比较单一的工具,本身并不处理任何具体的任务,只有当我们为其附加各种插件之后,它才具有实用性。 + +PostCSS 就像是一个使能器(enabler),它可以不用完全替代现有的预处理器或后处理器,而只是作为它们的补充工具。PostCSS 的工作机制主要包含解析代码、执行插件、渲染结果三部分: +![image](http://www.w3cplus.com/sites/default/files/blogs/2017/1707/figure-18.png) +PostCSS 会将 CSS 代码解析成包含一系列节点的抽象语法树(AST,Abstract Syntax Tree)。树上的每一个节点都是 CSS 代码中某个属性的符号化表示。换言之,如果你写了条件语句并对应三种结果,那么在抽象语法树中就会有一个包含三个分支的节点,每个分支就是符号化表示的结果。 + +### 功能 +将css中的dpx转换成px,dpx (dpr px) 这个单位, 按照 dpr 来放大 1px, 2px, 3*px 大小的字体。如下 +```css +css + .cls { + width: 75px; + font-size: 12dpx + border: 1rpx + } + +===> 转换为 + +css + .cls { + width: 2rem; + border: 1px; + } + [data-dpr="1"] .cls { font-size: 12px } + [data-dpr="2"] .cls { font-size: 24px } + [data-dpr="3"] .cls { font-size: 36px } +``` +### 实现 +安装postcss npm install --save-dev postcss + +使用postcss提供的api +walkRules 遍历容器的后代节点,为每个规则节点调用回调。 +walkDecls 遍历容器的后代节点,为每个声明节点调用回调。 +insertAfter 在容器中依次插入新节点。 +rule.append 在容器中依次插入新声明。 +postcss.rule 新建一个规则节点 + +创建方法遍历css规则,匹配其中的dpx,获取其中的值进行运算插入容器中 +根目录下创建component文件夹,文件夹下创建index.js +index.js +```javascript +var postcss = require('postcss'); + +const defaultOpts = { + prefix: 'data-dpr', + maxDpr: 3, + delete: true +}; + + +module.exports = function(opts, root) { + let options = { ...defaultOpts, ...opts }; + let insertRule = insertDpx(options.prefix, options.maxDpr); + root.walkRules((rule) => { + let declList = []; + rule.walkDecls((decl) => { + let data = getDpx(decl); + if (data) { + declList.push(data); + if (options.delete) { + decl.remove(); + } else { + decl.value = data.value + 'px'; + } + } + }) + insertRule(rule, declList); + }) +}; + + +const getDpx = (decl) => { + let regDpx = /(\d+)(dpx)([\s]+|[;]|$)/; + let value = decl.value; + if (value.match(regDpx)) { + let num = 0; + value.replace(regDpx, function(a, b) { + num = parseFloat(b); + }) + return { + prop: decl.prop, + value: num + } + } + return undefined; +} + +const insertDpx = (prefix, maxDpr) => (rule, declList) => { + if (!declList || declList.length === 0) { + return; + } + let unit = 'px'; + for (let i = maxDpr; i > 0; i--) { + let ruleName = `[${prefix}="${i}"] ${rule.selector}`; + let newRule = createRule(ruleName); + declList.forEach(decl => { + newRule.append({ prop: decl.prop, value: (decl.value * i) + unit }); + }); + rule.parent.insertAfter(rule, newRule); + } +} + +const createRule = function(name) { + return postcss.rule({ selector: name }) +} + +``` +### 参数 +prefix: 添加的属性名称 +maxDpr: 添加到的最大dpr, +delete: 是否删除原来的dpx属性 + +### 参考 +[postcss api](http://api.postcss.org/postcss.html) +[移动端高清、多屏适配方案](http://www.html-js.com/article/Mobile-terminal-H5-mobile-terminal-HD-multi-screen-adaptation-scheme%203041) +[响应式网页开发基础:DPR 与 viewport](https://zhuanlan.zhihu.com/p/26131956) +[等比例缩放rem](https://www.cnblogs.com/wellsoho/p/5099623.html) +[postcss](http://www.w3cplus.com/preprocessor/postcss-book.html) +[viewport的深入理解](http://www.cnblogs.com/2050/p/3877280.html) \ No newline at end of file From 35d1a496238195fb201362fced7e5fa6e963f0e0 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Tue, 19 Jun 2018 09:57:42 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- README.md | 2 +- "\345\256\236\347\216\260.md" | 49 ++++++++++++++++------------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 93c5291..45a12f5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ h1 { ### 安装 npm install --save-dev postcss-plugin-pxtorem -### 配置 +### 配置使用 ###### webpack配置 webpack rules添加 postcss-loader配置 ```js diff --git "a/\345\256\236\347\216\260.md" "b/\345\256\236\347\216\260.md" index 2d1f1a2..c8a7388 100644 --- "a/\345\256\236\347\216\260.md" +++ "b/\345\256\236\347\216\260.md" @@ -5,7 +5,7 @@ PostCSS 就像是一个使能器(enabler),它可以不用完全替代现 ![image](http://www.w3cplus.com/sites/default/files/blogs/2017/1707/figure-18.png) PostCSS 会将 CSS 代码解析成包含一系列节点的抽象语法树(AST,Abstract Syntax Tree)。树上的每一个节点都是 CSS 代码中某个属性的符号化表示。换言之,如果你写了条件语句并对应三种结果,那么在抽象语法树中就会有一个包含三个分支的节点,每个分支就是符号化表示的结果。 -### 功能 +### 需要实现的功能 将css中的dpx转换成px,dpx (dpr px) 这个单位, 按照 dpr 来放大 1px, 2px, 3*px 大小的字体。如下 ```css css @@ -49,27 +49,31 @@ const defaultOpts = { }; -module.exports = function(opts, root) { +module.exports = postcss.plugin('postcss-plugin-pxtorem', opts => { let options = { ...defaultOpts, ...opts }; - let insertRule = insertDpx(options.prefix, options.maxDpr); - root.walkRules((rule) => { - let declList = []; - rule.walkDecls((decl) => { - let data = getDpx(decl); - if (data) { - declList.push(data); - if (options.delete) { - decl.remove(); - } else { - decl.value = data.value + 'px'; + return root => { + let insertRule = insertDpx(options.prefix, options.maxDpr); + // 遍历规则 + root.walkRules((rule) => { + let declList = []; + // 遍历属性 + rule.walkDecls((decl) => { + let data = getDpx(decl); + if (data) { + declList.push(data); + if (options.delete) { + decl.remove(); + } else { + decl.value = data.value + 'px'; + } } - } + }) + insertRule(rule, declList); }) - insertRule(rule, declList); - }) + } }; - +// 获取规则中dpx值 const getDpx = (decl) => { let regDpx = /(\d+)(dpx)([\s]+|[;]|$)/; let value = decl.value; @@ -86,6 +90,7 @@ const getDpx = (decl) => { return undefined; } +// 创建规则 const insertDpx = (prefix, maxDpr) => (rule, declList) => { if (!declList || declList.length === 0) { return; @@ -106,15 +111,7 @@ const createRule = function(name) { } ``` -### 参数 -prefix: 添加的属性名称 -maxDpr: 添加到的最大dpr, -delete: 是否删除原来的dpx属性 ### 参考 [postcss api](http://api.postcss.org/postcss.html) -[移动端高清、多屏适配方案](http://www.html-js.com/article/Mobile-terminal-H5-mobile-terminal-HD-multi-screen-adaptation-scheme%203041) -[响应式网页开发基础:DPR 与 viewport](https://zhuanlan.zhihu.com/p/26131956) -[等比例缩放rem](https://www.cnblogs.com/wellsoho/p/5099623.html) -[postcss](http://www.w3cplus.com/preprocessor/postcss-book.html) -[viewport的深入理解](http://www.cnblogs.com/2050/p/3877280.html) \ No newline at end of file +[postcss](http://www.w3cplus.com/preprocessor/postcss-book.html) \ No newline at end of file From 898adc48b7e9b1b73f42a5d1bee0a50439cbe939 Mon Sep 17 00:00:00 2001 From: sunxh <997854244@qq.com> Date: Tue, 19 Jun 2018 15:04:03 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: sunxh <997854244@qq.com> --- "\345\256\236\347\216\260.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\256\236\347\216\260.md" "b/\345\256\236\347\216\260.md" index c8a7388..951ec02 100644 --- "a/\345\256\236\347\216\260.md" +++ "b/\345\256\236\347\216\260.md" @@ -6,7 +6,7 @@ PostCSS 就像是一个使能器(enabler),它可以不用完全替代现 PostCSS 会将 CSS 代码解析成包含一系列节点的抽象语法树(AST,Abstract Syntax Tree)。树上的每一个节点都是 CSS 代码中某个属性的符号化表示。换言之,如果你写了条件语句并对应三种结果,那么在抽象语法树中就会有一个包含三个分支的节点,每个分支就是符号化表示的结果。 ### 需要实现的功能 -将css中的dpx转换成px,dpx (dpr px) 这个单位, 按照 dpr 来放大 1px, 2px, 3*px 大小的字体。如下 +将css中的dpx转换成px,dpx (dpr px) 这个单位, 按照 dpr 来放大 1px, 2px, 3*px 大小的字体。输出如下 ```css css .cls {