├── README.md ├── css ├── a.css ├── b.css ├── torem-a.css └── torem-b.css ├── index.js ├── package.json └── postcss-pxtorem.js /README.md: -------------------------------------------------------------------------------- 1 | # postcss-pxtorem 2 | 3 | ### 移动端px to rem 4 | 5 | 例如设计师给出640px的设计稿,写css样式的时候,就按照640px切图,单位px。 6 | 完工后,js或者css媒体查询,设定1rem的值。例如1rem == 40px。那么我们只 7 | 需要把css文件里面的px换算成rem就行。 而不需要在切图的过程中换算成rem。 8 | 9 | ### 安装 10 | 11 | git clone git@github.com:Aralic/postcss-pxtorem.git 12 | 13 | ### 初始化 14 | 15 | npm install 16 | node index.js init 17 | 18 | 当前目录生成pxtorem.json 配置文件 19 | 20 | { 21 | //待处理文件or路径 相对路径 22 | "files": ["./css/a.css", "./css/"], 23 | //默认根目录字体大小(px) 24 | "root_value": 20, 25 | //保留小数位 26 | "unit_precision": 5, 27 | //需要换算的属性,支持CSS属性,不限以下 28 | //"font", "font-size", "line-height", "letter-spacing" 29 | "prop_white_list": ["width", "height", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left"], 30 | //布尔值,是否替换掉属性值 31 | //默认直接替换属性 32 | "replace": true, 33 | //布尔值,是否替换media screen中的属性值 34 | //例如“@media screen and (max-width:240px)” 35 | "media_query": 36 | } 37 | 38 | ### 执行 39 | 40 | node index.js build 41 | 42 | ### TIPS 43 | 44 | 对css文件只匹配了小写的px,如果需要不转换pxtorem,则可以在书写css的时候,大写PX,这样浏览器是支持的。 45 | 46 | ### 参考 47 | 48 | https://github.com/stormtea123/viewtorem 49 | https://github.com/cuth/postcss-pxtorem 50 | 51 | ### 预期功能 52 | 53 | 1. 生成的文件,从当前目录剥离出来,再根目录新建output文件夹,生成的文件输出到这个文件夹 54 | 2. 缓存优化,已编译输出,但再未修改过的文件不再编译。提升性能。 55 | -------------------------------------------------------------------------------- /css/a.css: -------------------------------------------------------------------------------- 1 | a { 2 | width: 5rem; 3 | height: 0.005rem; 4 | background: red; 5 | border: 1px solid red; 6 | } 7 | 8 | @media screen and (max-width: 100px) { 9 | body { 10 | width: 5rem; 11 | } 12 | } -------------------------------------------------------------------------------- /css/b.css: -------------------------------------------------------------------------------- 1 | a { 2 | width: 5rem; 3 | height: 0.005rem; 4 | background: red; 5 | border: 1px solid red; 6 | } 7 | 8 | @media screen and (max-width: 100px) { 9 | body { 10 | width: 5rem; 11 | height: 5rem; 12 | } 13 | } -------------------------------------------------------------------------------- /css/torem-a.css: -------------------------------------------------------------------------------- 1 | a { 2 | width: 5rem; 3 | height: 0.005rem; 4 | background: red; 5 | border: 1px solid red; 6 | } 7 | 8 | @media screen and (max-width: 100px) { 9 | body { 10 | width: 5rem; 11 | } 12 | } -------------------------------------------------------------------------------- /css/torem-b.css: -------------------------------------------------------------------------------- 1 | a { 2 | width: 5rem; 3 | height: 0.005rem; 4 | background: red; 5 | border: 1px solid red; 6 | } 7 | 8 | @media screen and (max-width: 100px) { 9 | body { 10 | width: 5rem; 11 | height: 5rem; 12 | } 13 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @file pxtorem转换 4 | * @author aralic(aralic@163.com) 5 | * @date 2015-09-14 6 | */ 7 | 8 | // 引入模块 9 | var fs = require('fs'), 10 | path = require('path'), 11 | postcss = require('postcss'), 12 | util = require('util'), 13 | pxtorem = require('./postcss-pxtorem.js'); 14 | 15 | var BASE_DIR = __dirname, 16 | ARGV = process.argv; 17 | 18 | /** 19 | * hash表,用于对push到fileArr数组的待操作文件去重 20 | * @type {Object} 21 | */ 22 | var map = {}; 23 | /** 24 | * 待操作文件列表,数组项为路径文件名 25 | * @type {Array} 26 | */ 27 | var fileArr = []; 28 | // 配置项,生成pxtorem.json文件 29 | var CONFIG = { 30 | "files": ["./css/a.css"], 31 | "root_value": 20, 32 | "unit_precision": 5, 33 | "prop_white_list": ["width", "height"], 34 | "replace": true, 35 | "media_query": false 36 | } 37 | if (ARGV.length >= 3 && ARGV[2] === 'init') { 38 | // node index.js init 39 | init(); 40 | } else if (ARGV.length >= 3 && ARGV[2] === 'build') { 41 | // node index.js build 42 | build(); 43 | } else { 44 | console.error('please command node index.js init or node index.js build'); 45 | } 46 | // 初始化操作 47 | // 如果当前目录不存在pxtorem.json配置文件 48 | // 则由此CONFIG配置生成文件 49 | function init() { 50 | var promise = new Promise(function(resolve, reject) { 51 | fs.exists( 52 | BASE_DIR + '/pxtorem.json', 53 | function(bool) { 54 | // bool为false 文件不存在 启用resolve 55 | bool ? reject() : resolve(); 56 | } 57 | ); 58 | }); 59 | promise.then(function() { 60 | fs.writeFile( 61 | BASE_DIR + '/pxtorem.json', 62 | JSON.stringify(CONFIG, null, 4), 63 | function(err) { 64 | if (err) reject(err); 65 | console.log('create pxtorem.json success') 66 | } 67 | ); 68 | }, function() { 69 | console.log('pxtorem.json file exist!!!\nplease command node index.js build'); 70 | }).catch(function(err) { 71 | console.log(err); 72 | }); 73 | } 74 | // 根据配置文件,动态生成css文件 75 | function build() { 76 | if (isExistsFile('/pxtorem.json')) { 77 | var opts = require('./pxtorem.json'); 78 | initOpts(opts); 79 | } else { 80 | console.error('error,file not exist!'); 81 | } 82 | 83 | } 84 | /** 85 | * 对配置的文件进行初始化操作 86 | * @param {Object} opts pxtorem.json文件内容 87 | * @return {null} 88 | */ 89 | function initOpts(opts) { 90 | var optsFiles = opts.files; 91 | var promisePath = []; 92 | 93 | if (util.isArray(optsFiles) 94 | && optsFiles.length > 0) { 95 | // 从配置文件提取需要转换的css文件 96 | // push到fileArr数组中 97 | fileArr = optsFiles. 98 | filter(function(item) { 99 | return path.extname(item) === '.css'; 100 | }). 101 | map(function(file) { 102 | file = path.join(BASE_DIR, file); 103 | map[file] = true; 104 | return file; 105 | }); 106 | // 从配置文件提取需要转换的路径名 107 | // 读取路径命下面的所有css文件 108 | // 并把css文件push到fileArr数组中 109 | // 过滤到重复的css文件 110 | // 返回新数组[promise, promise] 111 | promisePath = optsFiles. 112 | filter(function(item) { 113 | return path.extname(item) === ''; 114 | }). 115 | map(function(dir) { 116 | return readDir(dir); 117 | }); 118 | allDone(promisePath, opts); 119 | } else { 120 | console.log('please write filepathname in pxtorem.json files []'); 121 | return; 122 | } 123 | } 124 | 125 | /** 126 | * 读取当前目录下面的所有文件,选取css文件 127 | * @param {string} dir 目录路径 128 | * @return {Object} promise对象 129 | */ 130 | function readDir(dir) { 131 | var promise = new Promise(function(resolve, reject) { 132 | fs.readdir(dir, function(err, files) { 133 | if (err) reject(err); 134 | resolve(files); 135 | }); 136 | }); 137 | 138 | promise = promise.then(function(files) { 139 | // 当文件名不含有torem- 140 | // 并且是CSS文件,把文件push到fileArr数组中 141 | files. 142 | filter(function(file) { 143 | return path.extname(file) === '.css' && file.indexOf('torem-') === -1; 144 | }). 145 | map(function(file) { 146 | return path.join(BASE_DIR, dir, file); 147 | }). 148 | forEach(function(file){ 149 | if (!map[file]) { 150 | fileArr.push(file); 151 | map[file] = true; 152 | } 153 | }); 154 | }); 155 | 156 | return promise; 157 | } 158 | // 读取在配置项里面的文件路径 159 | // 依次转换CSS文件 160 | // 编译输出 161 | function complie(filePath, opts) { 162 | var promise = new Promise(function(resolve, reject) { 163 | fs.readFile( 164 | filePath, 165 | 'utf8', 166 | function(err, data) { 167 | if (err) reject(err); 168 | resolve(data); 169 | } 170 | ); 171 | }) 172 | promise = promise.then(function(css) { 173 | // 调用postcss-pxtorem插件 174 | // 返回内容 175 | // 写入文件 176 | return postcss([pxtorem(opts)]).process(css).then(function(result) { 177 | var filename = path.basename(filePath, 'css'); 178 | var fileDirname = path.dirname(filePath); 179 | var newFile = path.join(fileDirname, 'torem-'+filename+'css'); 180 | return outFile(newFile, result.css); 181 | }); 182 | }); 183 | return promise; 184 | } 185 | 186 | // 编译好的内容写入文件 187 | function outFile(filename, css) { 188 | var promise = new Promise(function(resolve, reject) { 189 | fs.writeFile( 190 | filename, 191 | css, 192 | 'utf8', 193 | function(err) { 194 | if (err) reject(err); 195 | resolve(); 196 | } 197 | ); 198 | }); 199 | promise = promise.then(function() { 200 | console.log(filename+ ' create success!'); 201 | }); 202 | return promise; 203 | } 204 | 205 | // all done 206 | function allDone(promisePath, opts) { 207 | // 监听所有的css文件编译,当全部完成触发 208 | Promise.all(promisePath).then(function() { 209 | // 读取完路径并且push转换文件函数到数组arr中 210 | var eventQueen = []; 211 | eventQueen = fileArr.map(function(file) { 212 | return complie(file, opts); 213 | }); 214 | Promise.all(eventQueen).then(function() { 215 | console.log('all task done!') 216 | }); 217 | 218 | }); 219 | } 220 | // 判断配置文件是否存在 221 | function isExistsFile(filePath) { 222 | filePath = path.join(BASE_DIR, filePath); 223 | var exist = fs.existsSync(filePath); 224 | if (!exist) { 225 | return false; 226 | } else { 227 | return true; 228 | } 229 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-pxtorem", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Aralic", 10 | "license": "ISC", 11 | "dependencies": { 12 | "postcss": "^5.0.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /postcss-pxtorem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @file pxtorem转换 4 | * @author aralic(aralic@163.com) 5 | * @date 2015-09-14 6 | */ 7 | var postcss = require('postcss'); 8 | 9 | module.exports = postcss.plugin('postcss-pxtorem', function(opts) { 10 | 11 | return function(css, result) { 12 | var prop_white_list = opts.prop_white_list; 13 | var root_value = opts.root_value; 14 | var precision = opts.unit_precision; 15 | var optsReplace = opts.replace; 16 | var regex = /(\d+)?(\.)?\d+px/; 17 | var mediaReplace = opts.media_query; 18 | // 替换规则 19 | var toReplace = function(string) { 20 | var value = parseFloat(string); 21 | return toFixed(value/root_value, precision) + 'rem'; 22 | } 23 | // 直接rem替换原有属性值px 24 | if (optsReplace) { 25 | css.replaceValues(regex, { fast: 'px' , props: prop_white_list}, toReplace); 26 | } else { 27 | // 保留原有属性,兼容不支持rem的浏览器 28 | css.walkDecls(function(decl) { 29 | var prop = decl.prop; 30 | if (!prop_white_list.length) return; 31 | if (prop_white_list.indexOf(prop) === -1) return; 32 | decl.cloneBefore({prop: prop}); 33 | decl.value = decl.value.replace(regex, toReplace); 34 | }); 35 | 36 | } 37 | 38 | // 针对媒体查询media条件pxtorem 39 | if (mediaReplace) { 40 | css.walkAtRules(function(rule) { 41 | if (rule.name === 'media' && rule.type === 'atrule') { 42 | rule.params = rule.params.replace(regex, toReplace); 43 | } 44 | }); 45 | } 46 | } 47 | }); 48 | 49 | // 保留小数点位数 50 | function toFixed(number, precision) { 51 | var multiplier = Math.pow(10, precision + 1), 52 | wholeNumber = Math.floor(number * multiplier); 53 | return Math.round(wholeNumber / 10) * 10 / multiplier; 54 | } 55 | 56 | --------------------------------------------------------------------------------