本身動手——讓Less支持中文編碼

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

Node近幾年發展得很是迅猛,但仍是有些小缺憾,好比對中文編碼的支持。因爲底層API對中文編碼的不友好,使得不少Node的模塊(less)不能使用GBK編碼的文件。本文借鑑網上的通用作法,對Less模塊作一些改造,使其支持GBK文件。css

1. Node的文件編碼問題及通用解決方法

Node讀寫文件是經過File System API完成的,以讀接口爲例:html

/**
 *  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)

接口能夠傳入文件編碼,但目前支持的編碼有限,只有UTF八、ASCII等。若是要讀取GBK編碼的文件,只能使用文件原始的二進制Buffer來進行編碼解析(不指定encoding參數)。 文件寫入的問題也相似,須要對文本內容進行編碼後才能存入。 目前用得最多的字符編解碼模塊有iconv-lite(Github主頁),支持GBK等多種字符編碼,示例以下。node

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,解決文本編碼的問題,只須要在全部讀寫文本文件的地方進行編解碼便可。git

2.1. less-middleware

在Web服務端,Less文件是經過中間件less-middleware讀取的,中間件把文本內容傳入Less解析器,渲染出CSS後,才存入本地文件並返回給瀏覽器。針對中間件的改動很簡單,代碼以下:github

// /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文件。express

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文件是無效的,還需修改解析器自身。json

// /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參數傳入。爲此咱們查看解析器源碼,關鍵內容以下。api

// /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編碼,繞過這個問題,也不失爲一種選擇。

相關文章
相關標籤/搜索