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をインストールします。
npm init
npm install underscore --save
また、package.jsonの"scripts"
に"start": "node index.js"
を追加しておくと、npm start
でサーバを起動できるのでやっておくと少し手間が省けます。
サーバ実装
まずはwebサーバを起動できないと話にならないので、サーバを実装します。
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を見てもらうのが一番良いと思います。
テンプレート用の構文は3種類あり、標準では以下のように記述します。
//テンプレートに与えられた値をそのまま出力します。
let compiled = _.template('hello: <%= name %>');
compiled({name: 'moe'});
//=> "hello: moe"
//テンプレートに与えられた値をエスケープ(_.escape())して出力します。
let template = _.template('<b><%- value %></b>');
template({value: '<script>'});
//=> '<b><script></b>'
//テンプレートに与えられた値をJavaScriptとして評価します。
let compiled = _.template("<% print('Hello ' + epithet); %>");
compiled({epithet: 'stooge'});
//=> "Hello stooge"
標準のテンプレート構文(<% ... %>
,<%= ... %>
,<%- ... %>
)が嫌だという場合は、_.templateSettings
を変更することで、構文を好きなように変更することができます。
では実際にテンプレートを返すサーバを実装しましょう。先ほどの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を作成します。
<!DOCTYPE html>
<html>
<head>
<title><%- title %></title>
</head>
<body>
<h1><%- title %></h1>
<p><%- p %></p>
</body>
</html>
そして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
テンプレートを外部に切り出すことに成功しました。
最後に
一応自分が望むような外部のテンプレートを作ることはできましたが、この方法だと規模が大きくなると厳しいのではないかと思います。おとなしくフレームワーク使ったほうが良いのかも?