自己写的一个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"  ]}
 

原文地址:https://www.cnblogs.com/caoke/p/10999392.html

时间: 2024-10-06 15:21:12

自己写的一个css性能分析工具:tinycss的相关文章

自己写的一个ffmpeg时间戳分析工具

代码托管 https://github.com/gitgjogh/ffmpeg_debug_ts 使用环境 FFMPEG 用于获取视频时间戳. 可以从 zeranoe 上下载事先编译的版本.(注意把ffempg所在路径加入系统或用户的环境变量$PATH) zeranoe: http://ffmpeg.zeranoe.com/builds/ python + numpy + matplotlib 用于数据分析和画图显示. 懒得折腾numpy和matplotlib,可以安装 python-xy 或 

一个简易的C++性能分析工具

我们的服务器项目有好几万行代码,昨天想分析一下其性能瓶颈,看看有没可优化的地方. gcc 提供了 __PRETTY_FUNCTION__ 宏,放在哪个函数体内,就表示哪个函数的名字,和 __LINE__ 类似.利用这个宏,我们可以向每个函数体内插入几行代码,记录下被调用的 Log. 我用 Python 写了一个脚本,分析所有 .cpp 文件中的 function,在函数体前面加上三行代码 : #if __FUNC_CALL_LOG__ LOGOUT("FUNC CALL : %s\n"

前端和云端性能分析工具分析报告

性能测试工具的主要作用是通过模拟生产环境中的真实业务操作,对被测试系统实行压力负载测试,监视被 测试系统在不同业务.不同压力性能下的性能表现,找出潜在的性能瓶颈进行分析.优化. 客户端与服务器相当于两个人,通过信息来进行交流.由于初次见面不好意思直接交流,与是找来了中间传话人,客户端把信息告诉给传话人,由传话人来转达给服务器.那么服务器反馈的信息也由传话人转达给客户端.一般性能测试工具都需要录制或编写客户端行为脚本. 这样传达人就有了客户端的行为能力,从而假扮客户端来欺骗服务器,与之进行通信.有

三种Linux性能分析工具的比较

无论是在CPU设计.服务器研发还是存储系统开发的过程中,性能总是一个绕不过去的硬指标.很多时候,我们发现系统功能完备,但就是性能不尽如意,这时候就需要找到性能瓶颈.进行优化.首先我们需要结合硬件特点.操作系统和应用程序的特点深入了解系统内部的运行机制.数据流图和关键路径,最好找出核心模块.建立起抽象模型:接着需要利用各种性能分析工具,探测相关模块的热点路径.耗时统计和占比.在这方面,Linux操作系统自带了多种灵活又具有专对性的工具,此外一些厂家也开源了不少优秀的性能分析工具.下面就结合笔者最近

系统级性能分析工具perf的介绍与使用

测试环境:Ubuntu14.04  on VMWare Kernel:3.13.0-32 系统级性能优化通常包括两个阶段:性能剖析(performance profiling)和代码优化.性能剖析的目标是寻找性能瓶颈,查找引发性能问题的原因及热点代码.代码优化的目标是针对具体性能问题而优化代码或编译选项,以改善软件性能. 在性能剖析阶段,需要借助于现有的profiling工具,如perf等.在代码优化阶段往往需要借助开发者的经验,编写简洁高效的代码,甚至在汇编级别合理使用各种指令,合理安排各种指

性能分析工具-PerfView

Roslyn的PM(程序经理) Bill Chiles,Roslyn使用纯托管代码开发,但性能超过之前使用C++编写的原生实现,这有什么秘诀呢?他最近写了一篇文章叫做<Essential Performance Facts and .NET Framework Tips>里头推荐了一个性能分析工具<Improving Your App's Performance with PerfView>.PerfView能够收集Windows事件跟踪(ETW)数据来追踪程序的调用流向,这些程序

Java性能优化指南系列(二):Java 性能分析工具

进行JAVA程序性能分析的时候,我们一般都会使用各种不同的工具.它们大部分都是可视化的,使得我们可以直观地看到应用程序的内部和运行环境到底执行了什么操作,所以性能分析(性能调优)是依赖于工具的.在第2章,我强调了基于数据驱动的性能测试是非常重要的,我们必须测试应用的性能并理解每个指标的含义.性能分析和数据驱动非常类似,为了提升应用程序的性能,我们必须获取应用运行的相关数据.如何获取这些数据并理解它们是本章的主题.[本章重点介绍JDK中提供的性能分析工具] 操作系统工具及其分析 程序分析的起点并不

超全整理!Linux性能分析工具汇总合集

出于对Linux操作系统的兴趣,以及对底层知识的强烈欲望,因此整理了这篇文章.本文也可以作为检验基础知识的指标,另外文章涵盖了一个系统的方方面面.如果没有完善的计算机系统知识,网络知识和操作系统知识,文档中的工具,是不可能完全掌握的,另外对系统性能分析和优化是一个长期的系列. 本文档主要是结合Linux 大牛,Netflix 高级性能架构师 Brendan Gregg 更新 Linux 性能调优工具的博文,搜集Linux系统性能优化相关文章整理后的一篇综合性文章,主要是结合博文对涉及到的原理和性

linux命令(4):top 命令(性能分析工具)

linux 的top命令详解 简介 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户 终止该程序为止. 比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间 对任务进行排序:而且该命令的很多特性都