标签:目录 mes dem 选择 html als spl 语法 gif
## 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"
]
}
标签:目录 mes dem 选择 html als spl 语法 gif
原文地址:https://www.cnblogs.com/caoke/p/10999392.html