## 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" ]}
原文地址:https://www.cnblogs.com/caoke/p/10999392.html
时间: 2024-10-06 15:21:12