分享

解决less的data

 Painever 2014-06-28

 Bug由来

从1.4.0版本开始,less添加了一个很好用的内置函数:data-uri。这个函数可以自动将less代码中引用的图片进行base64编码。比如我们要引用一个小图片作为背景,我们可以这样写:

  .foo-icon { 
      background: data-uri('./img/foo.png') no-repeat; 
      *background": url(htt://g.tbcdn.cn/onlone-addr.png) no-repeat;
  }

编译过之后会自动转换为:

  .foo-icon { 
      background: url("data:image/png;base64,iVBORw0KG ... mCC") no-repeat;
      *background": url(htt://g.tbcdn.cn/onlone-addr.png) no-repeat;
  }

这个函数极大的便利了前端的开发工作。在此之前,遇到这个情况我们一般都是自己运行相关的工具 进行编码,然后将编码后的字符手工粘贴到less源文件中。这样做不仅使less源代码又长又臭,而且也笔记容易出错。当设计稿经常改动时,需要多次手工操作,很容易出现粘贴代码不全,粘贴了旧图片的代码导致图片编码没有更新等问题。

当我们团队发现这个新函数后,就像发现了新大陆一般,兴奋异常,立即将使用的grunt-contrib-less插件更新到最新版本,并在正在开发的项目中大量使用此函数。当我们用的正Hight的时候,一个坑悄然而至。

当时我们的项目目录是这样规划的

我们组的两个同学分别负责home页面和detail页面的开发工作。两人分别用data-uri函数编码各自页面对应的less文件引用的图片。一切都运行的很完美。但在我们切图的过程中UI同学应老大的要求把效果图进行了调整,而我们两个同学一看最新的效果图发现两个页面中有很大一部分图标都是相同的。所以大家决定把这部分公共图标提炼为公共组件,供各个页面共享。于是一个同学把这部分图标相关代码移动到了/lib/less/common/icons.less,之后两人将原来的图标样式代码删除,然后在顶部添加了对公共图标样式文件的引用: @import "src/lib/less/common/icons";

这时候我们惊讶的发现代码怎么都编译不过去了。无论我们将图片地址写成基于页面less的相对地址,还是基于icons.less的相对地址,还是基于项目目录的绝对地址都报找不到图片。文件没有被别的文件 引用的时候还可以工作的,怎么被引用了之后就不能工作了呢?经过多次尝试,并综合google搜索到信息,最后我们确定这个问题应该是less的一个Bug。

bug原因

当时我曾尝试自己修复这个Bug,但因为代码比较多,时间又比较紧,没有成功。我当时只能要么将着用,要么自己去手动编码。我被这个Bug恶心了很久,就像吃饭的时候吃出了不该有的东西一样。

终于项目不是那么忙了,可以腾出手来收拾它了。一开始我怀疑这是我们grunt文件的配置不合理造成的,缺少了必要的配制参数导致路径计算不正确。后来读LESS的源代码项发现即使把可能有用的配制信息加上还是有问题,所以这个可能排除了。最终把怀疑的焦点时转向了data-uri函数上。为了便于调试我创建了一个只有两个less文件的测试项目。两个文件的目录位置和内容如下:

   //src/lib/less/sub.less
   body {
      background: data-uri('./img/hot.png');
   }

   //src/page/main.less
   @import "src/lib/less/sub";

为了获取运行时的参数情况我们修改less中的less/lib/less/functions.js中的data-uri函数的代码
将参数信息输出到控制台。之后我们运行grunt less命令编译两个文件得到如下控制台输出。其中sub.less文件编译成功,而page.less则因为文件引用的图片找不到而编译失败。

通过分析控制台输出我们可以看到,传给data-uri函数的参数中less提供了四个必要的参数:

  * value               传给data-uri函数的参数
  * filename            运行data-uri函数的less文件名称
  * rootFilename        正在编译的、且引用了其他其他less文件的less文件
  * currentDirectory    运行data-uri函数的less文件所在的目录
  * entryPath           正在编译的、且引用了其他其他less文件的less文件所在的目录

而且通过对比两个文件的这四个参数我们可以发现一个规律:如果less文件没有被其他less文件所引用,那么rootFilename与filename的值是一样的

有了这些信息之后按说我们完全有能力采用两种不同的策略来计算计算要引用的文件的绝对路径。如果文件没有被其他文件引用, valueentryPath运算计算出绝对路径;如果文件被其他文件所引用,valuecurrentDirectory运算计算出绝对路径。那less是怎么处理的呢?答案是没有处理!!!下面是less中这部分的处理代码

在上面的代码片段中,less首先判断传入的文件路径(也就是参数value的值)是否是相对路径,如果是再判断下是否配置了url路径到本地路径的映射。如果有则将URL路径替换为本地路径,然后计算计算出绝对地址(这个功能我们一直没用过, 读代码才发现有这个功能,所以这个参数没有配置); 如果没有配置,则不管文件是否被引用,全部按运行data-uri函数的less文件所在的目录进行计算。至此案情算是水落石出了。真想不通,实现这个函数的人怎么会采用这么简单粗暴的实现呢?在css中文件即使被其他文件以@import "filename.css";的方式引用了了,引用的图片的绝对路径也还是按照css文件所在的路径计算的啊。

解决方案

我们临时写了一个插件:grunt-myless, 以替换掉grunt自动的less插件, 其中对less的data-uri函数做了如下修改:

    tree.functions['data-uri'] = function(mimetypeNode, filePathNode) {        
        if (typeof window !== 'undefined') {
            return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
        }

        // edit code start ------------------
        var isFileInc;
        var mimetype = mimetypeNode.value;
        var fileInfo = this.currentFileInfo;
        var filePath = (filePathNode && filePathNode.value);

        var fs = require('fs'),
            path = require('path'),
            useBase64 = false;

        if (arguments.length < 2) {
            filePath = mimetype;
        }

        if (this.env.isPathRelative(filePath)) {
            isFileInc = (function(){
                var filename = fileInfo.filename.replace(/\\/g, '/');
                var rootName = fileInfo.rootFilename.replace(/\\/g, '/');
                return filename != rootName;
            })();  

            if (fileInfo.relativeUrls) {
                filePath = path.join(fileInfo.currentDirectory, filePath);
            } else {
                filePath = (isFileInc
                    ? path.join(fileInfo.currentDirectory, filePath)
                    : path.join(fileInfo.entryPath, filePath)
                );
            }
        }
        // edit code end --------------------

        // detect the mimetype if not given
        if (arguments.length < 2) {
            var mime;
            try {
                mime = require('mime');
            } catch (ex) {
                mime = tree._mime;
            }

            mimetype = mime.lookup(filePath);

            // use base 64 unless it's an ASCII or UTF-8 format
            var charset = mime.charsets.lookup(mimetype);
            useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
            if (useBase64) { mimetype += ';base64'; }
        }
        else {
            useBase64 = /;base64$/.test(mimetype);
        }

        var buf = fs.readFileSync(filePath);

        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
        // and the --ieCompat flag is enabled, return a normal url() instead.
        var DATA_URI_MAX_KB = 32,
        fileSizeInKB = parseInt((buf.length / 1024), 10);
        if (fileSizeInKB >= DATA_URI_MAX_KB) {

            if (this.env.ieCompat !== false) {
                if (!this.env.silent) {
                    console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
                }

                return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
            }
        }

        buf = useBase64 ? buf.toString('base64')
                        : encodeURIComponent(buf);

        var uri = "\"data:" + mimetype + ',' + buf + "\"";
        return new(tree.URL)(new(tree.Anonymous)(uri));
    };  

注意使用的时候,需要在gruntfile.js中添加如下配置:

 
当然如果不想更换插件自己手动修改less的源代码效果也是一样的,但那样的话每次项目初始化之后都需要自己手动修改,这点有点不爽。

原文链接: http://www./articles/17513

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多