自己动手——让Less支持中文编码

源Blog链接:http://home4j.duapp.com/index.php/2014/05/25/diy-a-less-with-multi-encoding-support.html

Node近几年发展得非常迅猛,但还是有些小缺憾,比如对中文编码的支持。由于底层API对中文编码的不友好,使得很多Node的模块(less)不能使用GBK编码的文件。本文借鉴网上的通用做法,对Less模块做一些改造,使其支持GBK文件。

1. Node的文件编码问题及通用解决方法

Node读写文件是通过File System API完成的,以读接口为例:

/**
 *  filename String
 *  options Object
 *     encoding String | Null default = null
 *       If no encoding is specified, then the raw buffer is returned.
 *     flag String default = 'r'
 *  callback Function
 */
fs.readFile(filename, [options], callback)

接口可以传入文件编码,但目前支持的编码有限,只有UTF8、ASCII等。如果要读取GBK编码的文件,只能使用文件原始的二进制Buffer来进行编码解析(不指定encoding参数)。 文件写入的问题也类似,需要对文本内容进行编码后才能存入。 目前用得最多的字符编解码模块有iconv-lite(Github主页),支持GBK等多种字符编码,示例如下。

var iconv = require('iconv-lite');

// Convert from an encoded buffer to js string.
var str = iconv.decode(buf, 'win1251');

// Convert from js string to an encoded buffer.
var buf = iconv.encode("Sample input string", 'win1251');

// Check if encoding is supported
var isExist = iconv.encodingExists("us-ascii");

2. DIY解决编码问题

通过iconv-lite,解决文本编码的问题,只需要在所有读写文本文件的地方进行编解码即可。

2.1. less-middleware

在Web服务端,Less文件是通过中间件less-middleware读取的,中间件把文本内容传入Less解析器,渲染出CSS后,才存入本地文件并返回给浏览器。针对中间件的改动很简单,代码如下:

// /less-middleware/lib/middleware.js
var iconv = require('iconv-lite');

// Less中间件参数
options = extend(true, {
  ...
  // 文件编码参数
  encoding: 'utf-8',
  ...
  // 存储编译后的CSS
  storeCss: function(pathname, css, next) {
    ...
    // 编码后再保存文件
    var buf = iconv.encode(css, options.encoding);
    fs.writeFile(pathname, buf, next);
  });
}, options || {});

// 编译Less文件
var compile = function() {
  fs.readFile(lessPath, function(err, buf){
    ...
    // 编译前先对文件内容解码
    var lessSrc = iconv.decode(buf, options.encoding);
    lessSrc = options.preprocess.less(lessSrc, req);
    render(lessSrc, lessPath, cssPath, function(err, css){
      ...
    });
  });
};

使用中间件时,只需添加encoding参数,即可支持GBK编码的Less文件。

var express = require('express');
var less = require('less-middleware');
...
var app = express();
var publicDir = __dirname + '/public';
app.use(less(publicDir, {
  encoding: 'gbk'
}));
...

2.2. Less解析器

Less有一个Import功能,可以引入其他Less或CSS文件到当前文件中,以实现代码的复用。被Import的文件是在Less解析器中读取的,因此前一节Less中间件的调整对Import文件是无效的,还需修改解析器自身。

// /less/lib/less/index.js
/**
 * fileLoader函数用于加载Less代码中被Import的文件。
 * env 为编译器的执行环境,用以保存执行参数
 */
less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
  ...
  var isUrl = isUrlRe.test( file );
  if (isUrl || isUrlRe.test(currentFileInfo.currentDirectory)) {
    // 如果Import的地址是URL格式,则从网络加载
    ...
  } else {
    // Import本地文件的处理逻辑
    ...
    // 分同步和异步两种方式读取Import文件
    if (env.syncImport) {
      ...
      var buf = fs.readFileSync(pathname);
      data = iconv.decode(buf, env.encoding || 'utf-8');
      ...
    } else {
      ...
      fs.readFile(pathname, function(e, buf) {
        ...
        data = iconv.decode(buf, env.encoding || 'utf-8');
        ...
      });
    }
  }
};

Less解析器的fileLoader函数用于加载文件,简单改动即实现功能,剩下的问题是如何把encoding参数传入。为此我们查看解析器源码,关键内容如下。

// /less/lib/less/parser.js
// 构造解析器时传入env参数
less.Parser = function Parser(env) {
  ...
  // 把env参数转化成parseEnv对象
  if (!(env instanceof tree.parseEnv)) {
    env = new tree.parseEnv(env);
  }
  ...
  less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) {
    ...
  }, env);
  ...
  return parser;
};

进而查看tree.parseEnv 的实现,发现需要把encoding参数添加到保留的属性列表中。

// /less/lib/less/env.js
(function (tree) {
  // 定义需要保留的属性
  var parseCopyProperties = [
    'paths',      // option - unmodified - paths to search for imports on
    // 把encoding参数加入
    'encoding',     // encoding used to read/write files
    'optimization',   // option - optimization level (for the chunker)
    ...
  ];

  tree.parseEnv = function(options) {
    // 根据要保留的属性列表parseCopyProperties,
    // 把options中的参数复制到新的parseEnv对象中(通过this引用)
    copyFromOriginal(options, this, parseCopyProperties);
    ...
  };
  ...
})(require('./tree'));

最后,在less-middleware使用Less解析器处传入编码参数,Less中文编码问题就基本解决了。

// /less-middleware/lib/middleware.js
...
module.exports = less.middleware = function(source, options, parserOptions, compilerOptions){
  // Parse and compile the CSS from the source string.
  var render = function(str, lessPath, cssPath, callback) {
    // 构造Less解析器
    var parser = new less.Parser(extend({}, options.parser, {
      filename: lessPath,
      encoding: options.encoding // 传入编码参数
    }));
    ...
  };
  ...
};

3. 代码的使用

本文对less-middleware和less的修改已提交到Git@OSC上。

less-middleware,http://git.oschina.net/joshuazhan/less.js-middleware

less,http://git.oschina.net/joshuazhan/less.js

同时,Node支持以git路径作为依赖,示例如下:

// /package.json
{
  ...
  "dependencies": {
    ...
    "less-middleware": "git+http://git.oschina.net/joshuazhan/less.js-middleware.git",
    ...
  }
}

有需要的同学可以直接使用。

4. 一点感想

Node文件编码格式问题的解决,本文是最次的一种方式,更为理想的是在Node的File System API上改进。当然如果是新应用或系统,使用UTF8编码,绕过这个问题,也不失为一种选择。

转载于:https://my.oschina.net/joshuazhan/blog/268971

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值