zoukankan      html  css  js  c++  java
  • 自己写的一个css性能分析工具:tinycss


    ## tinycss 功能
    css文件被引入了那些html页面
    css选择器在这些html页面的使用情况
    最后生产压缩后的css、以及map

    ### 技术栈
    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

    ### 不足:
    不支持去重,不支持选择器、属性去重

    app,js
    // 1、输入:所有的html、css, 遍历css文件。
    // 2、拿到a.css,找出存在a.css的htmlFileArr、htmlTextArr数组。
    // 3、将a.css、htmlFileArr、htmlTextArr放入TinyCss,生产优化后的a.css、a.map
    // 4、遍历2、3,得到优化后的所有css
    const TinyCss=require('./utils/TinyCss');
    
    const fs=require('fs');
    const path=require('path');
    const glob=require('glob');
    const mkdir=require('./utils/mkdir');
    
    function getText(filepath){
        return fs.readFileSync(filepath).toString();
    }
    const srcDir='./src/';
    //多个html文件
    const htmlFileArr=glob.sync(srcDir+'**/*.html');
    if(htmlFileArr.length==0){return;}
    
    const cssFileArr=glob.sync(srcDir+'**/*.css');
    if(cssFileArr.length==0){return;}
    console.log(cssFileArr)
    
    const toCssFileArr=cssFileArr.map(function (filepath) {
        return filepath.replace(srcDir,'./dist/');
    })
    
    //统计
    const vacancyArr=[];
    
    function build(htmlFileArr2,htmlTextArr,cssFile,cssText){
        //启动
        const app=new TinyCss(htmlTextArr,cssText);
    //输出
        const toText={
            vacancy:null,
            nouseKeyFrames:app.getEmptyKeyFrames(),
            nouseCss:app.getEmptyCss(),
            htmlFileArr:htmlFileArr2,
            map:app.showMap(),
        }
        toText.vacancy=[toText.nouseCss.length,toText.map.length]
        console.log('build:'+cssFile.replace('./dist/',''),'无用selector比率',toText.nouseCss.length+"/"+toText.map.length,'页面引用率',htmlFileArr2.length+"/"+htmlFileArr.length)
        vacancyArr.push([cssFile.replace('./dist/',''),'无用selector比率',toText.nouseCss.length+"/"+toText.map.length,'页面引用率',htmlFileArr2.length+"/"+htmlFileArr.length])
        mkdir(cssFile);
        fs.writeFileSync(cssFile,app.getTinyAst().toString());
        fs.writeFileSync(cssFile.replace(/css$/,'map'),JSON.stringify(toText,null,2))
    }
    
    cssFileArr.forEach(function (cssFile2,i) {
        const cssname=path.basename(cssFile2)
        const cssText2=getText(cssFile2)
        const htmlFileArr2=[]
        const htmlTextArr2=[]
        htmlFileArr.forEach(function (filepath,i) {
            const html=getText(filepath)
            if(html.indexOf(cssname)>-1){
                htmlFileArr2.push(htmlFileArr[i])
                htmlTextArr2.push(getText(htmlFileArr[i]))
            }
        })
    
        build(htmlFileArr2,htmlTextArr2,toCssFileArr[i],cssText2)
    
    });
    
    const tinyMap=vacancyArr.map(function (item) {
        return item.join(',')
    })
    
    fs.writeFileSync('./dist/tiny.map',JSON.stringify(tinyMap,null,2));

      

     TinyCss.js

    //TinyCss.js
    const Api=require('./Api');
    //解析成语法树
    const compiler = require('vue-template-compiler');
    const postcss = require('postcss');
    const querySelectorList=require('./querySelectorList')

    //构建出一个css语法树和多个html语法书,分析css的使用率。
    class TinyCss{
    constructor(htmlTextArr,cssText){

    //多个html书法树
    this.htmlTextArr=htmlTextArr;

    //一个css书法树
    this.cssAst=postcss.parse(cssText);
    this.cssList=Api.depthSearch(this.cssAst,'nodes').filter(function (node) {
    return node.type==='rule'&&!/keyframes/.test(node.parent.name);
    })

    //输出的部分
    this.bigMap=null;
    this.map=null;
    this.data=null;
    this.emptyCss=null;
    this.emptyKeyFrames=null;


    }

    //移除数组中的子元素
    removeObj(item,arr){
    for(let i=0;i<arr.length;i++){
    if(arr[i]===item){
    arr.splice(i,1)
    break;
    }
    }
    }
    //获取矩阵数据
    getBigMap(){
    if(this.bigMap){
    return this.bigMap;
    }
    let map=[];
    for(let i=0;i<this.htmlTextArr.length;i++){
    const htmlAst=compiler.compile(this.htmlTextArr[i]).ast;
    const ccRect=new querySelectorList(htmlAst,this.cssList);
    const rect=ccRect.analysis();
    map.push(rect)
    }
    this.bigMap=map;
    return map;
    }
    //获取小数据,矩阵数据
    getMap(){
    if(this.map){
    return this.map;
    }
    let map=[];
    for(let i=0;i<this.htmlTextArr.length;i++){
    const htmlText=this.htmlTextArr[i];
    const htmlAst=compiler.compile(htmlText).ast;
    const ccRect=new querySelectorList(htmlAst,this.cssList);
    const arr=ccRect.analysis().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])
    }
    }
    this.map=map;
    return map;
    }
    getUiMap(selector){
    if(this.uiMap){
    return this.uiMap;
    }
    let map=[];
    for(let i=0;i<this.htmlTextArr.length;i++){
    const htmlText=this.htmlTextArr[i];
    const htmlAst=compiler.compile(htmlText).ast;
    const ccRect=new querySelectorList(htmlAst,this.cssList);
    const uiArr=ccRect.querySelectorAndChild(selector)
    const arr=ccRect.analysis().map(function (item) {
    let index=0;
    for(let k=0;k<item.length;k++){
    if(item[k]===1&&uiArr[k]===1){
    index++;
    }
    }
    return index;
    });
    for(let j=0;j<arr.length;j++){
    if(!map[j])map[j]=[];
    map[j].push(arr[j])
    }
    }
    this.uiMap=map;
    return map;
    }
    //移除无用的css
    getEmptyCss(selector){
    if(this.emptyCss){
    return this.emptyCss;
    }
    const cssList=this.cssList;
    const data=[];
    const map=selector?this.getUiMap(selector):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(cssList[i],cssList[i].parent.nodes);
    data.push(cssList[i].selector);
    }
    }

    this.emptyCss=data;
    return data;
    }
    //移除空的动画
    getEmptyKeyFrames(){
    if(this.emptyKeyFrames){
    return this.emptyKeyFrames;
    }
    const keyframesList=Api.depthSearch(this.cssAst,'nodes').filter(function (node) {
    return node.type==='atrule'&&/keyframes/.test(node.name);
    })
    const vals=Api.depthSearch(this.cssAst,'nodes').filter(function (node) {
    return node.type==='decl'&&/animation/.test(node.prop);
    })
    const delArr=keyframesList.filter(function (node) {
    return !vals.some(function (node2) {
    return node2.value.split(' ').indexOf(node.params)>-1
    })
    })
    const emptyKeyFrames=[];
    delArr.forEach( (node) =>{
    //从ast中移除节点
    this.removeObj(node,node.parent.nodes);
    emptyKeyFrames.push('@'+node.name+' '+node.params)
    })
    this.emptyKeyFrames=emptyKeyFrames;
    return emptyKeyFrames;
    }
    //移除注释
    removeComment(){
    const commentArr=Api.depthSearch(this.cssAst,'nodes').filter(function (node) {
    return node.type==='comment';
    })
    commentArr.forEach((node)=>{
    this.removeObj(node,node.parent.nodes);
    })
    }
    getTinyAst(selector){
    this.getEmptyCss(selector);
    this.getEmptyKeyFrames();
    this.removeComment();

    return this.cssAst;
    }
    }
    module.exports=TinyCss;
    querySelectorList.js
    //querySelectorList.js
    const Api=require('./Api');
    //命中规则

    /*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 querySelectorList{

    constructor(htmlAst,cssList){

    //记录selector查找历史
    this.selectotCache={};

    //构建html语法树和矩阵bitmap
    this.htmlAst=htmlAst;
    this.htmlList=Api.depthSearch(this.htmlAst).filter(function (node) {
    return node.type===1;
    })

    //构建css语法树和矩阵bitmap
    this.cssList=cssList;
    }
    //分析
    analysis(){
    const cssList=this.cssList;
    const map=[]
    for(let i=0;i<cssList.length;i++){
    map[i]=this.querySelector(cssList[i].selector);
    }
    return map;
    }
    //获取选择器和它得子元素
    querySelectorAndChild(selector){
    const arr=this.querySelector(selector);
    for(let i=0;i<arr.length;i++){
    if(arr[i]===1){
    const cLen=Api.depthSearch(this.htmlList[arr[i]]).filter(function (node) {
    return node.type===1;
    }).length;
    for(let k=1;k<cLen;k++){
    i++;
    arr[i]=1;
    }
    }
    }
    return arr;
    }
    //可能是多选择器
    querySelector(selector){
    if(/,/.test(selector)){
    const arr=selector.split(',');
    const data=[];
    for(let i=0;i<arr.length;i++){
    const item=this.queryOneSelector(arr[i]);
    for(let k=0;k<item.length;k++){
    if(item[k]===1){
    data[k]=1;
    }else{
    data[k]=0;
    }
    }
    }
    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(/ ::/.test(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);
    })
    }
    //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;
    // console.log(selector,'属性选择器,只判断是否存在属性')
    arr.push(astNode.attrsMap.hasOwnProperty(key));
    }
    //伪类选择器
    if(/(:.+)/.test(selector)){
    const key=RegExp.$1;
    // console.log(selector,'解析->',selector.replace(/:.+$/,''))
    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=querySelectorList;

    //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
    }
    //treeSearch.js
    //广度遍历html节点
    function breadthSearch(item, childProp='children'){
        const nodeList=[item]
        let index=0;
        while (index<nodeList.length){
            const node=nodeList[index++];
            if(node[childProp]){
                for(let k in node[childProp]){
                    nodeList.push(node[childProp][k]);
                }
            }
        }
        return nodeList;
    }
    //深度遍历html节点
    function depthSearch(node,childProp='children'){
        const nodeList=[]
        const depthEach=function(item){
            nodeList.push(item);
            if(item[childProp]){
                for(let k in item[childProp]){
                    depthEach(item[childProp][k]);
                }
            }
        }
        depthEach(node);
        return nodeList;
    }
    
    module.exports={
        breadthSearch,depthSearch
    }
  • 相关阅读:
    BNUOJ 12756 Social Holidaying(二分匹配)
    HDU 1114 Piggy-Bank(完全背包)
    HDU 2844 Coins (多重背包)
    HDU 2602 Bone Collector(01背包)
    HDU 1171 Big Event in HDU(01背包)
    HDU 2571 命运 (入门dp)
    HDU 1069 Monkey and Banana(最长递减子序列)
    HDU 1160 FatMouse's Speed (最长上升子序列)
    HDU 2594 KMP
    POJ 3783 Balls --扔鸡蛋问题 经典DP
  • 原文地址:https://www.cnblogs.com/caoke/p/10999392.html
Copyright © 2011-2022 走看看