Node.jsでLESSファイルを動的にコンパイルする

Less & Sass Advent calendar 2011」9日目のです。
流れをぶった切ってしまってあれなのですが、Node.jsとあわせてLESSファイルを動的にコンパイルするようなやつ書きます。

みなさんご存知の通りLESSはJSで書かれているので、Node.jsを使ったアプリでは動的にLESSファイルをコンパイルできます。

ところがLESSのサイトに書いてある方法は

var parser = new(less.Parser);

parser.parse('.class { width: 1 + 1 }', function (err, tree) {
    if (err) { return console.error(err) }
    console.log(tree.toCSS());
});

・・・やさしくない!

まあでも早い話、parse(hoge, func)のhogeにLESSファイルの文字列渡せばtreeに結果が入ってくるってことですね。

これはLESSの記事というかNode.jsの記事じゃないのとかそういう細かいことはいいじゃないですか・・・

LESSファイルをコンパイルできるようになるまで

LESSファイルの場所

Expressコマンドで作ったテンプレートを使うことにします。Expressコマンドを実行すると(テンプレエンジンはjqtplにしてます)

$ express -t jqtpl less
// less
├ app.js
├ package.json
├ public
│ ├ images
│ ├ javascripts
│ └ stylesheets
├ routes
│ └ index.js
└ views

みたいにもろもろ出来ます。肝心のLESSファイルは views ディレクトリに less ディレクトリを作ってそこに入れることにしましょう。

// less
├ app.js
├ package.json
├ public
│ ├ images
│ ├ javascripts
│ └ stylesheets
├ routes
│ └ index.js
└ views
  ├ tmpl // ここにjqtplテンプレファイル入れる
  └ less // ここにLESSファイル入れる

less ディレクトリに次の内容の styles.less を作って入れておきます。

.class {
    width: 1 + 1px;
}

a {
  text-decoration: underline;

  &:hover {
    text-decoration: none;
  }
}

ちなみに、今

http://localhost:3000(ここは環境によります)/less/styles.less

にアクセスしてみると、当然のように表示できません。

LESSのインストール

とりあえず最初から順を追っていきます。まずはnpmを使ってLESSをインストールしておきます。

$ npm install less@latest

LESSをrequireする

この記事ではJSコードは app.js に書いています。インストールしたらLESSが使えるようになるので、LESSをrequireします。ついでに、今回はpathとfsも使っているので一緒にrequireしておきます。

最後に今回使った app.js をそのまま貼ってるので詳しくはそっち見てください。

var less = require('less'),
    path = require('path'),
    fs = require('fs');

LESSファイルにアクセスしたときのなんやらを設定

まずはファイルにアクセスする前の準備です。

app.get(/^\/less\/.+/, function(req, res) {
// less/hoge にアクセスした場合
  var fileRoot = __dirname + '/views/less/', // less ファイルのディレクトリ
      extname = path.extname(req.url), // .less の部分が返る
      basename = path.basename(req.url), // styles.less の部分が返る
      filePath = undefined;

  if ( extname !== '.less' ) {
  // lessファイルじゃなかったら・・・
    console.log('that is not less file');
  }

  filePath = fileRoot + basename; // パスを取得しておく
  // ここに続き書く
});

ファイルパスまで準備できたので、次はファイルを読み込みます。ファイルを読み込めたら、一旦LESSをパースしてCSSへ変換、その後変換したCSSをファイルとして返すようにします。

app.get(......
  ........

  fs.readFile(filePath, 'utf8', function(err, str) {
  // str で読み込んだ LESSファイルのバッファを受け取る
    var parser = undefined;

    if ( err ) {
    // 読み込み先でエラーがあったら・・・
      console.log('file doesn\'t exist: ' + basename);
    }

    parser = new(less.Parser);
    // LESSのパーサー

    parser.parse(str, function(err, tree) {
    // LESSファイル の文字列部分を渡す
    // tree にパースした LESSのオブジェクトを受け取る
      var css = undefined;

      if ( err ) {
        // パースでエラーがあれば・・・
        console.log('parse error: ' + err.message);
      }

      // toCSS()メソッドを実行するとCSSが返る
      css = tree.toCSS();

      res.writeHead(200, {
        'Content-Type': 'text/css'
      });
      res.end(css, 'utf-8');
      // CSSをファイルとして返す
    });
  });
});

実行して、アクセスしてみる

http://localhost:3000/less/styles.less にアクセスしてみましょう。

.class {
  width: 2px;
}
a {
  text-decoration: underline;
}
a:hover {
  text-decoration: none;
}

完璧っぽい!

CSSを圧縮しておきたい場合

ちなみに、toCSS()メソッドのオプションで compress 指定をすると圧縮したCSSファイルを返せます。

css = tree.toCSS({ compress: true });

圧縮されたCSSが返ります。ナイス。

.class{width:2px;}
a{text-decoration:underline;}a:hover{text-decoration:none;}

おわり

自分でやってるときはかなり時間かかってしまったんですけど、、、記事にするとかなり短くて凹みました・・・あ、@import とかするとおうふな感じになるので、それはまたどこかで書きます。。

付録:今回使った app.js と HTML(jqtpl)/LESSファイル

app.js は Expressコマンドで出来るやつをベースに使ってます。

app.js

/**
 * Module dependencies.
 */

var express = require('express')
  , jqtpl = require('jqtpl')
  , less = require('less')
  , path = require('path')
  , fs = require('fs')
  , routes = require('./routes')

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views/tmpl');
  app.set('view engine', 'html');
  app.set('view options', {
    layout: false
  });
  app.register('.html', jqtpl.express);
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);

app.get(/^\/less\/.+/, function(req, res) {
  var fileRoot = __dirname + '/views/less/',
      extname = path.extname(req.url),
      basename = path.basename(req.url),
      filePath = undefined;

  if ( extname !== '.less' ) {
    console.log('that is not less file');
  }

  filePath = fileRoot + basename;

  fs.readFile(filePath, 'utf8', function(err, str) {
    var parser = undefined;

    if ( err ) {
      console.log('file doesn\'t exist: ' + basename);
    }

    parser = new(less.Parser);

    parser.parse(str, function(err, tree) {
      var css = undefined;

      if ( err ) {
        console.log('parse error: ' + err.message);
      }

      css = tree.toCSS({ compress: true });

      res.writeHead(200, {
        'Content-Type': 'text/css'
      });
      res.end(css, 'utf-8');
    });
  });
});

app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

index.html

index.html は views/tmpl/index.html におきます。




        
        ${title}
        


${title}

Google

styles.less

styles.less は views/less/styles.less におきます。

.class {
    width: 1 + 1px;
}

a {
  text-decoration: underline;

  &:hover {
    text-decoration: none;
  }
}

何か役に立つことがあったらシェアしてみてください

このエントリーをはてなブックマークに追加

Leave a comment

Trackbacks: 0

Trackback URL for this entry
Listed below are links to weblogs that reference
Node.jsでLESSファイルを動的にコンパイルする from 5509

Author

nori
nori
- UI Engineer
Location
- ,