简体   繁体   English

CSS类使用php集成重命名

[英]CSS Class Renaming with php integration

I have a php project which uses grunt to compile sass files into css. 我有一个php项目,它使用grunt将sass文件编译成css。 I was wondering if there is a way of doing css class renaming similar to Google's Closure Stylesheets . 我想知道是否有一种类似于Google的Closure Stylesheets进行css类重命名的方法。 So really this is a two-part question: 所以这真是一个由两部分组成的问题:

  1. How to compile sass with shortened class names. 如何使用缩短的类名编译sass。

    As far as I know sass currently doesn't have any feature like this, unless it can be added as an extension. 据我所知,sass目前没有这样的功能,除非它可以作为扩展添加。 However with grunt I could compile the sass files first, then run another task that does the class renaming and outputs a map file. 然而,随着grunt,我可以先编译sass文件,然后运行另一个执行类重命名的任务并输出一个map文件。 Technically I could use Closure Stylesheets for this, but I am looking for something a little more lightweight that doesn't require installing another dependency. 从技术上讲,我可以使用Closure Stylesheets,但我正在寻找一些更轻量级的东西,不需要安装另一个依赖项。

  2. How to include these class names in php. 如何在php中包含这些类名。

    Now I could just insert something like this for every css class: <?php echo getclassname("some-class-name") ?> which would reference the map file generated above to get the correct class name. 现在我可以为每个css类插入这样的东西: <?php echo getclassname("some-class-name") ?> ,它将引用上面生成的映射文件以获得正确的类名。 But that seems tedious. 但这似乎很乏味。 Is there a better way of doing this? 有没有更好的方法呢?

How to compile sass with shortened class names 如何使用缩短的类名编译sass

First compile the sass then passing it through a custom task to rename the classes. 首先编译sass然后通过自定义任务传递它来重命名类。 I am using the css node module to parse the css. 我正在使用css节点模块来解析css。 Let's start by looking at the custom grunt task. 让我们从查看自定义grunt任务开始。

Disclaimer: I wrote this code quickly so it is probably not production ready. 免责声明:我快速编写了这段代码,因此可能没有准备好生产。

var fs     = require( 'fs' ),
    rename = require( './rename.js' );

// Register the rename_css task.
grunt.registerMultiTask('rename_css', 'Shorten css class names', function () {
    var options = this.options(); // Pass all options directly to css.parse
    this.files.forEach(function ( file ) {
        var renamed = rename.rename(
            fs.readFileSync( file.src[ 0 ], 'utf8' ), options );
        fs.writeFileSync( file.dest, renamed.text );
        fs.writeFileSync( file.map, JSON.stringify( renamed.map, null, 2 ) );
    });
});

The configuration for this task would look something like this: 此任务的配置如下所示:

grunt.initConfig({
    rename_css: {
        options: { compress: true }, // Minify the output css.
        main: {
            src: "style.css",
            dest: "style.min.css",
            map: "map.json"
        }
    }
});

rename.js is too long to show it all here, but you can see the entire file on github . rename.js太长了,无法在这里显示,但你可以在github上看到整个文件。 Here is the main function: 这是主要功能:

function rename( s, options /* passed directly to css.parse */ ) {
    /**
     * Give the css classes short names like a-b instead of some-class
     * 
     * Returns an object in the form {text: `newCss`, map: `partsMap`} whare text is
     * the newly generated css and partsMap is a map in the {oldPart: newPart}.
     */
    var 
        ast = css.parse( s, options ),
        countMap = walkPass1( ast.stylesheet ), // Walk the first pass.
        sortedCounts = [],
        map = {}, // Final map.
        part,

        // List of charictor positions for the short class names.
        // Each number corresponds to a charictor in the `chars` string.
        charPosSet = [ 0 ];

    // Unpack the count map.
    for ( part in countMap ) {
        sortedCounts.push({
            name: part,
            count: countMap[ part ],
            replacment: undefined
        });
    }
    // Sort based on the number of counts. 
    // That way we can give the most used classes the smallest names.
    sortedCounts.sort(function( a, b ) { return b.count - a.count });

    // Generate the small class names.
    sortedCounts.forEach(function ( part ) {
        var 
            s = '',
            i = charPosSet.length;
        // Build up the replacment name.
        charPosSet.forEach(function ( pos ) {
            s += chars[ pos ];
        });

        while ( i-- ) {
            charPosSet[ i ]++;
            // If the current char pos is greater then the lenght of `chars`
            // Then we set it to zero.
            if ( charPosSet[ i ] == chars.length ) {
                charPosSet[ i ] = 0;
                if ( i == 0 ) { // Time to add another digit.
                    charPosSet.push( 0 ); // The next digit will start at zero.
                }
            } else {
                // Everything is in bounds so break the loop.
                break;
            }
        }
        part.replacment = s;
    });

    // Now we pack a basic map in the form of old -> new.
    sortedCounts.forEach(function ( part ) {
        map[ part.name ] = part.replacment;
    });

    // Walk the tree a second time actually renameing the classes.
    walkPass2( ast.stylesheet, map );

    return {
        text: css.stringify( ast, options ), // Rebuild the css.
        map: map
    };
}

It looks compicated but here is a break-down of what it is doing: 它看起来很复杂,但这里是它正在做的事情的细分:

  1. Parse the css and get the Abstract Syntax Tree(ast). 解析css并获取抽象语法树(ast)。
  2. Walk the tree creating a map of class parts to counts (the number of occurenses within a css document). 走树,创建一个类部件的映射计数(css文档中的事件数)。
  3. Pack the map into an array and sort it based on the counts. 将地图打包成数组并根据计数对其进行排序。
  4. Iterate over the array creating the shortened class names. 迭代数组创建缩短的类名。
  5. Create the final map in the form of oldName -> newName 以oldName - > newName的形式创建最终地图
  6. Walk the tree a second time actually replacing the old class names with the new ones. 第二次走树,实际上用新的类名替换旧的类名。
  7. Return the compiled css along with the generated map. 将已编译的css与生成的地图一起返回。

It's worth pointing out that this function will give more frequently used classes shorter names, which will result in slightly smaller css files. 值得指出的是,此函数将为更常用的类提供更短的名称,这将导致更小的css文件。

How to include these class names in php. 如何在php中包含这些类名。

This can be done with an output buffer. 这可以通过输出缓冲区完成。 It might look something like this (at the top of the page before the root html tag): 它可能看起来像这样(在根html标记之前的页面顶部):

<?php

define(DEV_MODE, false);

function build_class( $name, $map ) {
    $parts = [];
    foreach ( explode( '-', $name ) as $part ) {
        $newPart = array_key_exists( $part, $map )? $map[ $part ] : $part;
        array_push( $parts, $newPart );
    }
    return implode( '-', $parts );
}
function class_rename ( $content ) {
    $string = file_get_contents( 'map.json' );
    $classMap = json_decode( $string, true );

    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false; // Remove unnesesary whitespace.

    @$doc->loadHTML( $content );
    foreach ( $doc->getElementsByTagName( '*' ) as $elem ) {
        $classStr = $elem->getAttribute( 'class' );
        if ( ! empty( $classStr ) ) { // No need setting empty classess all over the place.
            $classes = []; // This is ware we put all the renamed classes.
            foreach ( explode( ' ', $classStr ) as $class ) {
                array_push( $classes, build_class( $class, $classMap ) );
            }
            $elem->setAttribute( 'class', implode( ' ', $classes ) );
        }
    }

    return $doc->saveHTML();
}
if (!DEV_MODE)
    ob_start( 'class_rename' );
?>

JavaScript (bonus) JavaScript(奖金)

Although not a part of the original question, the solution is quite interesting and not exactly trivial so I decided to include it. 虽然不是原始问题的一部分,但解决方案非常有趣,并不是非常简单,所以我决定将其包括在内。

First of all register another grunt task: 首先注册另一个grunt任务:

var fnPattern = /(jQuery|\$|find|__)\s*\(\s*(["'])((?:\\.|(?!\2).)*)\2\s*\)/g;

grunt.registerMultiTask('rename_js', 'Use short css class names.', function () {
    this.files.forEach(function ( file ) {
        var 
            content = fs.readFileSync( file.src[ 0 ], 'utf8' ),
            map = JSON.parse( fs.readFileSync( file.map ) ),

        output = content.replace( fnPattern, function ( match, fn, delimiter, str ) {
            var classes, i;
            if ( fn == '__' ) {
                classes = str.split( ' ' );
                i = classes.length;

                while ( i-- ) {
                    classes[ i ] = rename.getClassName( classes[i], map );
                }

                // We can safly assume that that the classes string won't contain any quotes.
                return '"' + classes.join( ' ' ) + '"';
            } else { // Must be a jQuery function.
                return match.replace( str, rename.getClassSelector( str, map ) );
            }
        });
        // Wrap the output in a function so that the `__` function can get removed by an optimizer.
        fs.writeFileSync( file.dest, '!(function(window, undefined) {\n' + output + '\n})(window);' );
    });
});

The JavaScript file might look something like this: JavaScript文件可能如下所示:

function __( s ) {
    return s;
}

window.main = function () {
    var elems = document.getElementsByClassName(__('some-class-name')),
        i = elems.length;
    while ( i-- ) {
        elems[ i ].className += __(' some-other-class-name');
    }
}

The important part is the __ function declaration. 重要的部分是__函数声明。 During development this function will do nothing, but when we build the app, this function will get replaced with the compiled class string. 在开发期间,此函数将不执行任何操作,但是当我们构建应用程序时,此函数将替换为已编译的类字符串。 The regex expression used will find all occurrences of __ as well as the jQuery functions ( jQuery , $ and jQuery.find ). 使用的正则表达式将找到所有出现的__以及jQuery函数( jQuery$jQuery.find )。 It then creates three groups: The function name, the delimiter (either " or ' ) and the inside string. Here is a diagram to help better understand the what's going on: 然后它创建三个组:函数名称,分隔符( "' )和内部字符串。这是一个图表,以帮助更好地了解正在发生的事情:

(?:jQuery|\$|find)\s*\(\s*(["'])((?:\\.|(?!\1).)*)\1\s*\)

正则表达式可视化

Debuggex Demo Debuggex演示

If the function name is __ then we replace it the same way we did for the php. 如果函数名是__那么我们用与php相同的方式替换它。 If not then it is probably a selector so we try to do a selector class replace. 如果没有那么它可能是一个选择器,所以我们尝试做一个选择器类替换。

(Note that this does not handle html text getting inputted into a jQuery function.) (注意,这不会处理输入到jQuery函数的html文本。)

You can get a full example here 你可以在这里得到一个完整的例子

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM