【问题标题】:CSS Class Renaming with php integrationCSS 类重命名与 php 集成
【发布时间】:2015-04-12 03:51:19
【问题描述】:

我有一个 php 项目,它使用 grunt 将 sass 文件编译成 css。我想知道是否有一种类似于 Google 的 Closure Stylesheets 的 CSS 类重命名方法。所以实际上这是一个由两部分组成的问题:

  1. 如何用缩短的类名编译 sass。

    据我所知,sass 目前没有这样的功能,除非它可以作为扩展添加。但是,使用 grunt 我可以先编译 sass 文件,然后运行另一个任务来重命名类并输出映射文件。从技术上讲,我可以为此使用闭包样式表,但我正在寻找更轻量级的东西,不需要安装其他依赖项。

  2. 如何在 php 中包含这些类名。

    现在我可以为每个 css 类插入类似的内容:<?php echo getclassname("some-class-name") ?>,它将引用上面生成的映射文件以获得正确的类名。但这似乎很乏味。有更好的方法吗?

【问题讨论】:

    标签: php css sass gruntjs


    【解决方案1】:

    如何用缩短的类名编译 sass

    首先编译 sass,然后将其传递给自定义任务以重命名类。 我正在使用css 节点模块来解析css。 让我们从查看自定义 grunt 任务开始。

    免责声明:我快速编写了这段代码,所以它可能还没有准备好生产。

    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 ) );
        });
    });
    

    此任务的配置如下所示:

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

    rename.js 太长,无法在此处全部显示,但您可以在 github 上查看整个文件。这是主要功能:

    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
        };
    }
    

    它看起来很复杂,但这里是它正在做的事情的分解:

    1. 解析 css 并获取抽象语法树 (ast)。
    2. 遍历树,创建类部分映射到计数(css 文档中出现的次数)。
    3. 将地图打包成一个数组并根据计数对其进行排序。
    4. 遍历数组创建缩短的类名。
    5. 以 oldName -> newName 的形式创建最终地图
    6. 第二次遍历树,实际上用新的类名替换旧的类名。
    7. 将编译后的 css 与生成的地图一起返回。

    值得指出的是,这个函数会给更常用的类提供更短的名称,这将 导致 css 文件稍小。

    如何在 php 中包含这些类名。

    这可以通过输出缓冲区来完成。它可能看起来像这样(在页面顶部的根 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(奖励)

    虽然不是原始问题的一部分,但该解决方案非常有趣且并非微不足道,因此我决定将其包含在内。

    首先注册另一个 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);' );
        });
    });
    

    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');
        }
    }
    

    重要的部分是__ 函数声明。在开发过程中这个函数什么都不做,但是当我们构建 应用程序,这个函数将被编译的类字符串替换。使用的正则表达式将找到所有出现的 __ 以及 jQuery 函数(jQuery$jQuery.find)。然后它创建三个组:函数名, 分隔符("')和内部字符串。这是一个图表,可帮助您更好地了解正在发生的事情:

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

    Debuggex Demo

    如果函数名是__,那么我们用与 php.ini 相同的方式替换它。如果不是那么它是 可能是一个选择器,所以我们尝试做一个选择器类替换。

    (请注意,这不处理输入到 jQuery 函数中的 html 文本。)

    你可以得到一个完整的例子here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-04
      • 2010-11-27
      • 1970-01-01
      • 2015-07-28
      • 1970-01-01
      • 2017-11-05
      • 1970-01-01
      • 2014-11-02
      相关资源
      最近更新 更多