diff --git a/README.md b/README.md index f1aca77..45a12f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## 功能 -一个postcss插件,用来将css中的dpx 换算成px +基于postcss-plugin-px2rem 实现的一个postcss插件,用来将css中的dpx 换算成px以及将px转换成rem.保留了postcss-plugin-px2rem的所有配置。 ### 输入输出 @@ -12,7 +12,7 @@ h1 { // 输出 h1 { - + font-size: 16px } [data-dpr="1"] h1 { font-size: 16px @@ -23,11 +23,28 @@ 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; +} +``` +### 安装 +npm install --save-dev postcss-plugin-pxtorem -### webpack配置 -webpack rules添加 postcss-loader +### 配置使用 +###### webpack配置 +webpack rules添加 postcss-loader配置 ```js module.exports = { module: { @@ -39,34 +56,57 @@ module.exports = { } } ``` - -在根目录新增 `postcss.config.js` 文件: +##### postcss 配置 +在根目录新增 `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 -`maxDpr` 生成的dpr的最大值 默认值 3 -`delete` 是否删除匹配到的声明 默认值 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的属性。值需要完全匹配. + +`selectorBlackList` (Array) 如果值是字符串,则检查选择器是否包含字符串,如`[body]`将匹配`.body-class`。如果值是正则表达式,则检查选择器是否与该正则表达式匹配,如`[/^body$/]`将匹配`body`,但不匹配`.body`。 + +`ignoreIdentifier` (Boolean/String) 一种将单个属性忽略的方法,如果启用了不命名标识符,则“replace”将自动设置为“true”。 +`replace` (Boolean) 取代包含rems的规则,而不是添加后备项。 +`mediaQuery` (Boolean) 允许在媒体查询中转换PX。 +`minPixelValue` (Number) 设置要替换的最小像素值。 ## 测试 -控制台输入 npm test \ No newline at end of file +控制台输入 npm run test 测试dpx转px以及px转rem \ No newline at end of file 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..0410636 100644 --- a/src/index.js +++ b/lib/dpxtopx.js @@ -1,31 +1,31 @@ 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(); + } else { + decl.value = data.value + 'px'; } - }) - insertRule(rule, declList); + } }) - } -}); + insertRule(rule, declList); + }) +}; const getDpx = (decl) => { @@ -44,13 +44,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..90021fc 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,26 @@ { - "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" + "test": "mocha", + "test-rem": "mocha ./test/index-test.js" }, "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": "", - "license": "ISC", + "author": { + "name": "sunxh", + "email": "997854244@qq.com" + }, + "license": "MIT", "bugs": { "url": "https://github.com/sunxhh/postcss-plugin-pxtorem/issues" }, @@ -29,5 +33,7 @@ "expect": "^23.0.0", "mocha": "^5.2.0" }, - "dependencies": {} + "dependencies": { + "postcss-plugin-pxtorem": "0.0.1" + } } 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..1892f7e 100644 --- a/test/test.js +++ b/test/test.js @@ -2,10 +2,13 @@ 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', () => { + 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(basicDpxCSS).css; expect(processed).toBe(expected); }); }); -describe('prevName', () => { +describe('prefix', () => { it('测试prevName', () => { const options = { - prevName: "test" + prefix: "test", + pxtorem: false }; const expected = `.rule { } @@ -40,7 +44,7 @@ describe('prevName', () => { [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); }); @@ -50,7 +54,8 @@ describe('maxDpr', () => { it('测试最大dpr', () => { const options = { - maxDpr: 4 + maxDpr: 4, + pxtorem: false }; const expected = `.rule { } @@ -66,23 +71,24 @@ 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); }); }); describe('delete', () => { const options = { - delete: false + delete: false, + pxtorem: false }; 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 }`; - 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 diff --git "a/\345\256\236\347\216\260.md" "b/\345\256\236\347\216\260.md" new file mode 100644 index 0000000..951ec02 --- /dev/null +++ "b/\345\256\236\347\216\260.md" @@ -0,0 +1,117 @@ +### 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 = postcss.plugin('postcss-plugin-pxtorem', opts => { + let options = { ...defaultOpts, ...opts }; + 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); + }) + } +}; + +// 获取规则中dpx值 +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 }) +} + +``` + +### 参考 +[postcss api](http://api.postcss.org/postcss.html) +[postcss](http://www.w3cplus.com/preprocessor/postcss-book.html) \ No newline at end of file