自己写的一个css性能分析工具:tinycss
Posted caoke
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己写的一个css性能分析工具:tinycss相关的知识,希望对你有一定的参考价值。
## tinycss
分析一个css文件在多个html页面的使用情况,输出有效的css文件
### 技术栈
glob、postcss、vue-template-compiler
### 开发准备
1、git clone本项目~
2、cnpm i
### 使用说明:
1、src目录放入html文件(一个以上)、css文件(一个以上)
2、npm run test //执行命令,输出dist目录
3、demo.css是压缩后的css文件,demo.map是矩阵数据(记录css的命中情况)
### 注意点
不支持所有伪类(除了:root),例如:div.class:first-child 等同于 div.class
### 不足:
不过滤动画选择器:keyframes
不支持去重,不支持选择器、属性去重
核心类CssRect.js
const Api=require(‘./Api‘); //解析成语法树 const compiler = require(‘vue-template-compiler‘); const postcss = require(‘postcss‘); /*css rule矩阵,3*6 行对应selector[‘.id‘,‘.class1‘,‘.class2‘] 列对应html节点 [‘body‘,‘body div‘,‘body div div‘,‘body div p‘,‘body div span‘,‘body div span a‘] [ [0,0,0,0,1,0], [0,0,0,0,1,0], [0,0,0,0,1,0] ] */ class CssRect constructor(htmlText,cssText,debug) //log this.logCache= this.debug=debug; //记录selector查找历史 this.selectotCache=; this.cssText=cssText; //构建html语法树和矩阵bitmap this.htmlAst=compiler.compile(htmlText).ast; this.htmlList=Api.depthSearch(this.htmlAst).filter(function (node) return node.type===1; ) //构建css语法树和矩阵bitmap const cssObj=CssRect.getCssAstAndList(cssText); this.cssAst=cssObj.cssAst; this.cssList=cssObj.cssList static getCssAstAndList(cssText) const obj= obj.cssAst=postcss.parse(cssText); obj.cssList=Api.depthSearch(obj.cssAst,‘nodes‘).filter(function (node) return node.type===‘rule‘&&!/keyframes/.test(node.parent.name); ) return obj; //分析 analysis() const cssList=this.cssList; const map=[] for(let i=0;i<cssList.length;i++) map[i]=this.querySelector(cssList[i].selector); return map; //可能是多选择器 querySelector(selector) if(/,/.test(selector)) const arr=selector.split(‘,‘); const data=this.queryOneSelector(arr[0]); for(let i=1;i<arr.length;i++) const item=this.queryOneSelector(arr[i]); for(let k=0;k<data.length;k++) if(data[k]==0) data[k]=item[k]; return data; else return this.queryOneSelector(selector) //查询css_rule,返回[array astNode] queryOneSelector(selector) selector=selector.trim();//去掉左右空格 //解析css rule const selectorArr=[] selector.replace(/(.+?)([ >~\+]+(?!\d)|$)/ig,function (m,p1,p2) selectorArr.push(p1,p2); ) // console.log(selectorArr) this.selectorArr=selectorArr; // console.log(selectorArr) //设置缓存 let preSelector=‘‘; for(let i=0;i<selectorArr.length;i=i+2) const exec=selectorArr[i-1]||‘‘; const curSelector=selectorArr[i]; this.setSelectotCache(preSelector,exec,curSelector); preSelector=preSelector+exec+curSelector const arr=new Array(this.htmlList.length).fill(0); // if(‘:root body‘==selector) // console.log(selector,selectorArr) this.selectotCache[selector].forEach( (node) => arr[this.htmlList.indexOf(node)]=1; ) return arr; //记录selector查询html语法树 setSelectotCache(preSelector,exec,curSelector) const nextSelector=preSelector+exec+curSelector; //已有缓存 if(this.selectotCache[nextSelector])return; if(!preSelector&&!exec) this.selectotCache[curSelector]=this.breadthHit(curSelector,this.htmlAst) return; const arr=this.selectotCache[preSelector]; this.selectotCache[nextSelector]=[]; if(/^ +$/.test(exec)) arr.forEach((node)=> this.selectotCache[nextSelector]=this.selectotCache[nextSelector].concat(this.breadthHit(curSelector,node)); ) else if(/^ *> *$/.test(exec)) arr.forEach((node)=> this.selectotCache[nextSelector]+this.selectotCache[nextSelector].concat(this.childHit(curSelector,node)); ) else if(/^ *\+ *$/.test(exec)) arr.forEach((node)=> this.selectotCache[nextSelector]+this.selectotCache[nextSelector].concat(this.sublingHit(curSelector,node)); ) else if(/^ *~ *$/.test(exec)) arr.forEach((node)=> this.selectotCache[nextSelector]+this.selectotCache[nextSelector].concat(this.sublingsHit(curSelector,node)); ) else console.log(‘exec异常:‘+exec) //css_rule:element+element sublingHit(tag,astNode) if(!astNode.parent) return [astNode].filter( (node) => return this.hitNode(tag,node); ) return Api.nextSublingSearch(astNode,astNode.parent).filter( (node) => return this.hitNode(tag,node); ) //css_rule:element~element sublingsHit(tag,astNode) return Api.nextSublingsSearch(astNode,astNode.parent).filter(function (node) return this.hitNode(tag,node); ) //css_rule:element element breadthHit(tag,astNode) return Api.breadthSearch(astNode).filter( (node)=> return node.type===1&&this.hitNode(tag,node); ) //css_rule:element>element childHit(tag,astNode) return Api.childSearch(astNode).filter( (node)=> return node.type===1&&this.hitNode(tag,node); ) //log 一次 logOnce(key) if(!this.debug)return; if(this.logCache[key]) return; this.logCache[key]=true; console.log.apply(console,arguments) //tag是否命中ast节点,返回true、false hitNode(selector,astNode) //分割字符串 (tag)、(id、class)(val) if(selector===‘*‘) return true; else if(/:root/.test(selector)) return astNode.tag===‘html‘; else const arr=[]; //tag if(/(^[a-z]+)/i.test(selector)) const tag=RegExp.$1; arr.push(astNode.tag===tag) //class if(/\.([\w-]+)/.test(selector)) const val=RegExp.$1; arr.push(astNode.attrsMap.class&&astNode.attrsMap.class.split(‘ ‘).indexOf(val)>-1); //id if(/#(\w+)/.test(selector)) const val=RegExp.$1; arr.push(astNode.attrsMap.id===val); //属性 if(/\[([\w-]+)(~=|=||=)?(\w+)?\]/.test(selector)) const key=RegExp.$1; const exec=RegExp.$2; const val=RegExp.$3; this.logOnce(selector,‘属性选择器,只判断是否存在属性‘) arr.push(astNode.attrsMap[key]===true); //伪类选择器 if(/(\:.+)/.test(selector)) const key=RegExp.$1; this.logOnce(selector,‘伪类选择器,不解析‘) arr.push(true) // arr.push(astNode.attrsMap.id===val); if(arr.length==0) // console.log(this.selectorArr) console.log(selector,this.selectorArr,‘css 解析异常‘) return arr.every((item)=>item); module.exports=CssRect;
应用类TinyCss.js
const CssRect=require(‘./CssRect‘) //构建出一个css语法树和多个html语法书,分析css的使用率。 class TinyCss constructor(htmlTextArr,cssText) //多个html书法树 this.htmlTextArr=htmlTextArr; //一个css书法树 this.cssText=cssText; //移除数组中的子元素 removeObj(item,arr) for(let i=0;i<arr.length;i++) if(arr[i]===item) arr.splice(i,1) break; //获取矩阵数据 getBigMap() let map=[]; for(let i=0;i<this.htmlTextArr.length;i++) const htmlText=this.htmlTextArr[i]; const ccRect=new CssRect(htmlText,this.cssText); const rect=ccRect.analysis(); map.push(rect) return map; //获取小数据,矩阵数据 getMap() let map=[]; for(let i=0;i<this.htmlTextArr.length;i++) const htmlText=this.htmlTextArr[i]; const ccRect=new CssRect(htmlText,this.cssText); const rect=ccRect.analysis(); const arr=rect.map(function (item) return item.reduce((x,y)=>x+y); ); for(let j=0;j<arr.length;j++) if(!map[j])map[j]=[]; map[j].push(arr[j]) return map; //获取展示数据 showMap() const cssObj=CssRect.getCssAstAndList(this.cssText); const map=this.getMap(); for(let i=0;i<map.length;i++) map[i]=cssObj.cssList[i].selector+","+map[i].join(‘,‘); return map; //显示无用的css getEmptyCss() const cssObj=CssRect.getCssAstAndList(this.cssText); const data=[]; const map=this.getMap(); for(let i=0;i<map.length;i++) //存在比0大的就是用到的,都是0就是无用的css if(map[i].every(function (n) return n===0 )) //从ast中移除节点 this.removeObj(cssObj.cssList[i],cssObj.cssList[i].parent.nodes); data.push(cssObj.cssList[i].selector); this.tinyAst=cssObj.cssAst; return data; getTinyAst() if(!this.tinyAst) this.getEmptyCss(); return this.tinyAst; module.exports=TinyCss;
运行app.js
const TinyCss=require(‘./TinyCss‘) const fs=require(‘fs‘); const path=require(‘path‘); const glob=require(‘glob‘); //多个html文件 const htmlFileArr=glob.sync(‘./src/*.html‘); const htmlTextArr=htmlFileArr.map(function (filepath) return fs.readFileSync(filepath).toString() ) // //多个css文件 const cssFileArr=glob.sync(‘./src/*.css‘); // console.log(cssFileArr) const cssText=cssFileArr.map(function (filepath) return fs.readFileSync(filepath).toString() ).join(‘‘); //启动 const app=new TinyCss(htmlTextArr,cssText); // console.log(htmlFileArr) // console.log(app.showMap()) // console.log(app.getEmptyCss()) //输出 const toText= emptyCss:app.getEmptyCss(), showMap:app.showMap(), if(!fs.existsSync(‘./dist‘)) fs.mkdirSync(‘./dist‘); if(!fs.existsSync(‘./src‘)) fs.mkdirSync(‘./src‘); fs.writeFileSync(‘dist/‘+path.basename(cssFileArr[0]),app.getTinyAst().toString()); fs.writeFileSync(`dist/$path.basename(cssFileArr[0],‘css‘)map`,JSON.stringify(toText,null,2))
工具Api.js,节点查询相关,深度遍历和广度遍历
const treeSearch=require(‘./treeSearch‘); //遍历子节点 function childSearch(node,childProp=‘children‘) return node[childProp]; //遍历兄弟节点 function nextSublingsSearch(node,pnode,childProp=‘children‘) const parr=pnode[childProp].filter((node)=> return node.type===1 ); return parr.slice(parr.indexOf(node)+1); //遍历下一个兄弟节点 function nextSublingSearch(node,pnode,childProp=‘children‘) return nextSublingsSearch(node,pnode).slice(0,1); module.exports= childSearch, nextSublingsSearch, nextSublingSearch, ...treeSearch
数据输出:demo.map
"htmlFileArr": [
"./src/xsj.html",
"./src/years_carnival.html",
"./src/years_carnival1.html",
"./src/zhima_recognition.html"
],
"emptyCss": [
".modal-center .wrap",
".modal-center .flex1",
".modal-center .flex1:first-child",
".modal-center .flex1:last-child,\n.modal-center .flex1.non-border",
".modal .align-left,\n.home-page .t-content .des,\n.over.face .des-failed",
".modal.recognition .close",
".modal.recognition .wrap .flex1"
],
"showMap": [
".btn,\n.btn-left,\n.btn-right,0,2,2,2",
".btn.active,0,2,2,2",
"body,1,1,1,1",
".agree-pact span,0,0,0,5",
".agree-pact,\n.form-group .form-control .dt,0,0,0,1",
".modal-center .wrap,0,0,0,0",
".modal-center .flex1,0,0,0,0",
".modal-center .flex1:first-child,0,0,0,0",
".modal-center .flex1:last-child,\n.modal-center .flex1.non-border,0,0,0,0",
".btn,\n.btn-left,\n.btn-right,0,2,2,2",
".btn.active,0,2,2,2",
".btn.disabled-btn,0,2,2,2",
".modal.entrance,0,1,1,0",
".modal .align-left,\n.home-page .t-content .des,\n.over.face .des-failed,0,0,0,0",
".modal.recognition .close,0,0,0,0",
".modal.recognition .wrap .flex1,0,0,0,0",
".modal.vcode .btn,0,1,1,0",
".modal.vcode .btn.disabled-btn,0,1,1,0",
".modal.share .modal-content,0,1,1,0",
".modal.share .btn,0,1,1,0",
".btn,\n.btn-left,\n.btn-right,0,2,2,2",
".btn.active,0,2,2,2"
]
以上是关于自己写的一个css性能分析工具:tinycss的主要内容,如果未能解决你的问题,请参考以下文章