Node.jsでUnderscore.jsのtemplateを使う


Node.jsを勉強しているのですが、何か作ろうと思った時に静的なファイルを返すだけじゃなあ……と思っていたので、テンプレートエンジンを探していました。
どうやらUnderscore.jsでテンプレートエンジンのようなことができるということなので、実際に試してみました。

## 環境

```
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.11.6
BuildVersion:	15G1212

$ node --version
v7.3.0
```

Underscore.js 1.8.3

## 準備

まずは`npm init`でpackage.jsonを作ります。
その後、`npm install underscore --save`でUnderscoreをインストールします。

```bash
npm init
npm install underscore --save
```

また、package.jsonの`"scripts"`に`"start": "node index.js"`を追加しておくと、`npm start`でサーバを起動できるのでやっておくと少し手間が省けます。

## サーバ実装

まずはwebサーバを起動できないと話にならないので、サーバを実装します。

```javascript:index.js
const http = require('http'),
  url = require('url');

http.createServer(onRequest).listen(8080);
console.log('Server has started on 8080.');

function onRequest(request, response){
  let pathname = url.parse(request.url).pathname;
  console.log(`Request for ${pathname} received.`);
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.write(`<p>Request path name = ${pathname}</p>`);
  response.end();
}
```

これでlocalhost:8080にブラウザからアクセスすると、リクエストしたURLが表示されるようになりました。
試しに`npm start`してlocalhost:8080/fooにアクセスすると、 *Request path name = /foo* が表示されます。

次にUnderscoreのtemplateメソッドを使ってみましょう。

## _.template(templateString, [settings]) 

templateの使い方は[Underscorejs.org](http://underscorejs.org/#template)を見てもらうのが一番良いと思います。
テンプレート用の構文は3種類あり、標準では以下のように記述します。

```javascript
//テンプレートに与えられた値をそのまま出力します。
let compiled = _.template('hello: <%= name %>');
compiled({name: 'moe'});
//=> "hello: moe"

//テンプレートに与えられた値をエスケープ(_.escape())して出力します。
let template = _.template('<b><%- value %></b>');
template({value: '<script>'});
//=> '<b>&lt;script&gt;</b>'

//テンプレートに与えられた値をJavaScriptとして評価します。
let compiled = _.template("<% print('Hello ' + epithet); %>");
compiled({epithet: 'stooge'});
//=> "Hello stooge"
``` 

標準のテンプレート構文(`<% ... %>`,`<%= ... %>`,`<%- ... %>`)が嫌だという場合は、`_.templateSettings`を変更することで、構文を好きなように変更することができます。

では実際にテンプレートを返すサーバを実装しましょう。先ほどのindex.jsに変更を加えます。

```javascript:index.js
const http = require('http'),
  url = require('url'),
  __ = require('underscore');

http.createServer(onRequest).listen(8080);
console.log('Server has started on 8080.');

function onRequest(request, response){
  let pathname = url.parse(request.url).pathname,
    interpolate = __.template('<p>Request path name = <%= pathname %></p>'),
    evaluate = __.template("<p>Request path name's length = <% print(pathname.length); %></p>"),
    escape = __.template('<p>Escaped request path name = <%- pathname %>');
  console.log(`Request for ${pathname} received.`);
  response.writeHead(200, {'Content-Type': 'text/html'});
  //response.write(`<p>Request path name = ${pathname}</p>`);
  response.write(interpolate({pathname: pathname}));
  response.write(evaluate({pathname: pathname}));
  response.write(escape({pathname: pathname}));
  response.end();
}
```

Underscoreを`require()`するとき、変数名に`_`は使わないほうが良いでしょう。
Node.jsのREPLで`_`は予約語になっているためです。そのため、ここでは`__`としました。
REPLを使わないということであれば、変数名に`_`を使っても良いかもしれません。

`npm start`してサーバを起動し、localhost:8080/fooにアクセスした場合、以下の結果が得られます。

```
Request path name = /foo

Request path name's length = 4

Escaped request path name = /foo
```

パス名はエスケープされないのであまりありがたみを感じませんね……。例としては良くありませんでした。

## テンプレートを外部ファイルにする

さて、一応`_.template()`を使って動的なHTMLを作ることには成功しました。
ですがJavaScript内にHTMLがあるのはメンテナンス上嬉しくないと思いますので、外部ファイルに切り出しましょう。
以下のようなtemplate.htmlを作成します。

```html:template.html
<!DOCTYPE html>
<html>
  <head>
    <title><%- title %></title>
  </head>
  <body>
    <h1><%- title %></h1>
    <p><%- p %></p>
  </body>
</html>
```

そしてindex.jsを以下のように変更します。

```javascript:index.js
const http = require('http'),
  url = require('url'),
  __ = require('underscore'),
  fs = require('fs');

http.createServer(onRequest).listen(8080);
console.log('Server has started on 8080.');

function onRequest(request, response){
  let pathname = url.parse(request.url).pathname,
    interpolate = __.template('<p>Request path name = <%= pathname %></p>'),
    evaluate = __.template("<p>Request path name's length = <% print(pathname.length); %></p>"),
    escape = __.template('<p>Escaped request path name = <%- pathname %>');
  console.log(`Request for ${pathname} received.`);
  fs.readFile('template.html', 'utf-8', (err, data) => {
    if(err) {
      response.writeHead(404, {'Content-Type': 'text/plain'});
      response.write('Not Found');
      response.end();
    }else{
      response.writeHead(200, {'Content-Type': 'text/html'});
      //response.write(`<p>Request path name = ${pathname}</p>`);
      //response.write(interpolate({pathname: pathname}));
      //response.write(evaluate({pathname: pathname}));
      //response.write(escape({pathname: pathname}));
      response.write(__.template(data)({title: 'Title', p: 'paragraph'}));
      response.end();
    }
  });
}
```

fsを`require()`して、ローカルのファイル(template.html)から読み込んだテンプレートを`_.template()`に渡しています。
サーバにアクセスすると、以下のような結果が得られます。

```
Title

paragraph
```

テンプレートを外部に切り出すことに成功しました。

## 最後に

一応自分が望むような外部のテンプレートを作ることはできましたが、この方法だと規模が大きくなると厳しいのではないかと思います。おとなしくフレームワーク使ったほうが良いのかも？