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
    }
  • 相关阅读:
    严蔚敏数据结构习题3.14
    Effective C++ Item 34 Differentiate between inheritance of interface and inheritance of implementation
    Effective C++ Item 33 Avoid hiding inherited names
    Effective C++ Item 19 Treat class design as type design
    Effective C++ Item 18 Make interfaces easy to use correctly and hard to use incorrectly
    Effective C++ Item 17 Store newed objects in smart pointer in standalone statements
    Effective C++ Item 16 Use the same form in corresponding uses of new and delete
    Effective C++ Item 15 Provide access to raw resources in resource-managing classes
    Effective C++ Item 14 Think carefully about copying behavior in resource-managing classe
    Effective C++ Item 13 Use object to manage resources
  • 原文地址:https://www.cnblogs.com/caoke/p/10999392.html
Copyright © 2011-2022 走看看