ExpressはNode.jsでWebアプリを作成できるフレームワークです。今回、代表的なテンプレートエンジンであるEJSの使い方についてまとめました。将来の自分がコピペできるようにメモしておきます。
どんなシチュエーションでテンプレートエンジンを使うのか?
テンプレートエンジンは、テンプレートと呼ばれる雛形のファイルをもとに、外部のデータと組み合わせたファイルを生成できるソフトウェアのことを言います。
HTMLは本来、静的なウェブページしか作成できませんが、テンプレートエンジンを使うと、外部データと組み合わせた動的なページを作成することができます。
静的・動的ページの違い
静的なページとは、ファイルが更新されない限り、いつ誰がみても同じ内容が表示されるページです。当たり前といえばそうですが、動的ページはアクセスするユーザーや端末によって異なる内容が表示されます。
動的ページは、ユーザーがアクセスする度にHTMLファイルが生成されるのが特徴です。
EJSをインストールする
EJSを使うにはパッケージのインストールが必要です。基本的な使い方は公式サイトに載っています。
公式サイト
次のコマンドでインストールします。
$ npm i ejs
エラーが表示されなければインストール完了です!※package.jsonにもejsの記載があることを確認しておきます。
EJSの基本的な使い方【res.render】
テンプレートを使ってトップページを表示してみます。最低限のディレクトリ/ファイル構成がこちらです。
※関係のある箇所を色付けしました。
.
├── index.js // mainモジュール
├── node_modules
├── package-lock.json
├── package.json
└── views
└── index.ejs // テンプレートファイル
主に、index.jsとindex.ejsを編集します。
以下のコードをindex.jsに記載します。
※関係ある箇所を色付けしました。
index.js
const express = require("express");
const app = express();
// ejsテンプレートエンジンを設定
app.set("view engine", "ejs");
// getでルートディレクトリにリクエストがあったら
app.get("/", (req, res) => {
// indexテンプレートを返す
res.render("index");
});
// ポート3000でアクセスを待ち受ける
app.listen(3000, () => {
console.log("running port 3000");
});
テンプレートファイルはviewsディレクトリに格納することが予め決まってます。
viewsディレクトリを作成して、テンプレートファイルを格納します。拡張子はejsです。ここではindex.ejsを作成します。
/views/index.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>トップ</title>
</head>
<body>
<h1>トップ</h1>
</body>
</html>
コードは普通のHTMLと全く同じに見えます。このテンプレートをブラウザでアクセスした際に表示(レンダリング)されるようにします。
レンダリングするにはindex.jsでレスポンスをres.render(‘”)で返す必要があります。引数にはテンプレート名を指定します。※拡張子ejsは必要ありません
今回、index.ejsをレンダリングしたいので、res.render(‘index”)になります。
index.js
// getでルートディレクトリにリクエストがあったら
app.get('/', (req, res) => {
// indexテンプレートをレスポンスで返す
res.render('index'); // テンプレートの名前を設定 ※拡張子は必要なし
});
これで、viewsディレクトリにあるejsファイルをブラウザに表示することができます。
ブラウザで「localhost:3000」にアクセスすると、テンプレートで設定した<h1>トップ</h1>が表示されました。
外部からのデータをテンプレートに渡す方法
先ほど、res.render(‘”)でテンプレートindex.ejsをレンダリングしましたが、この状態だと静的ページとなり、テンプレートを使う意味がありません。
動的ページは、外部から渡されたデータと、テンプレートを組み合わせて表示することが基本です!試しに、ブラウザで表示させた「トップ」の文字を外部から受け取ったデータに差し替えます。
テンプレートに外部からのデータを渡すには、次のようにオブジェクトの形で渡します。
index.js
// getでルートディレクトリにリクエストがあったら
app.get("/", (req, res) => {
// indexテンプレートを返す
res.render("index", { title: "トップページですよ" });
});
index.ejs
テンプレートで受け取った値を表示するには、次のように渡されたプロパティを<%= %>で囲みます。
<h1><%= title %></h1>
そうするとプロパティの値が展開されて、“トップページですよ” の文字がブラウザに表示されます。
ファイルを共通化する【include()】
次のようにinclude()で他のテンプレートを読み込むことができます。
<head>や<header>、<footer>など、共通化したいコードでよく使います。もちろんテンプレートにデータを渡すこともできます。
index.ejs
<!DOCTYPE html>
<html lang="ja">
// head.ejsをインポートする
<%- include("./head", {metaTitle : "サンプルアプリのトップページ"}) %>
<body>
<main>
<h1><%= title %></h1>
</main>
</body>
</html>
こちらが、共通化したいテンプレートhead.ejsです。
/views/head.ejs
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
// 渡されたデータを表示
<title><%= metaTitle %></title>
</head>
ページの表示と、全体のファイル構成はこちらで確認できます!
<head>や<header>、<footer>も同じように共通化しました。
ファイル共通化のサンプル
テンプレートで使えるタグの種類
テンプレートで使える<%のタグですが、数種類あります。よく使う5種類をメモしておきます。
タグ | HTMLの出力 | |
<%= %> | 出力あり | 値をテンプレートに出力する(エスケープあり) |
<%- %> | 出力あり | 値をテンプレートに展開する(エスケープなし) |
<% %> | 出力なし | 制御用のコードを記述できる(改行も反映) |
<% -%> | 出力なし | 制御用のコードを記述できる(改行を削除) |
<%# %> | 出力なし | コメントを記述できる |
値をテンプレートに出力する(エスケープあり) <%= %>
変数の値を展開して出力します。その際、HTMLタグはエスケープされます。
出力前(EJS)
<% const text = "<p>テキスト</p>"; %>
<p>出力結果</p>
<%= text %>
出力後(HTML)
<p>出力結果</p>
<p>テキスト</p>
値をテンプレートに出力する(エスケープなし)<%- %>
変数の値を展開して出力します。その際、HTMLタグはエスケープされません。
出力前(EJS)
<% const text = "<p>テキスト</p>"; %>
<p>出力結果</p>
<%- text %>
出力後(HTML)
<p>出力結果</p>
<p>テキスト</p>
制御用のJavaScriptを記述する(改行あり)<% %>
制御用のJavaScriptを記述できるので、外部データの中身を取り出す時などに使います。
その際、タグの改行も出力されます。
出力前(EJS)
<% ["みかん", "ばなな","ぶどう"].forEach((fruit, i) => { %>
<p><%= i %>.<%= fruit %></p>
<% }) %>
出力後(HTML)
<p>0.みかん</p>
<p>1.ばなな</p>
<p>2.ぶどう</p>
制御用のJavaScriptを記述する(改行なし)<% -%>
制御用のJavaScriptを記述できます。<% %>との違いは改行をトリム(削除)してくれることです。
出力されたコードがスッキリします。
出力前(EJS)
<% ["みかん", "ばなな","ぶどう"].forEach((fruit, i) => { -%>
<p><%= i %>.<%= fruit %></p>
<% }) %>
出力後(HTML)
<p>0.みかん</p>
<p>1.ばなな</p>
<p>2.ぶどう</p>
それ以外のタグについては、公式サイトに詳しく載っています。
デフォルトのディレクトリ名を変更する
テンプレートを格納するデフォルトのディレクトリ名はviewsなのですが、ディレクトリ名を変更したい時もあると思います。
その場合、以下をindex.jsに追記します。
index.js
// viewsディレクトリの名称変更
app.set('views', path.join(__dirname, 'templates'));
これで、templatesディレクトリにテンプレートファイルを格納できるようになります。
簡単なフォームを作成する
テンプレートエンジンを使って簡単なフォームを作ってみます。
実際の動きを確認するフォームサンプル
CodeSandBoxで実際の動きを確認します
ポイントだけをメモしておきます!
フォームを送信したら、POSTメソッドで送信された値を受け取ります。
テンプレートresultに受け取った値を渡してレンダリングします。
GETメソッドで/resultにアクセスがあると、返却するデータがないので、トップにリダイレクトしておきます。
index.js
const path = require("path");
const express = require("express");
const app = express();
// フォームの値を解析する
app.use(express.urlencoded({ extended: true }));
// 静的フォルダを設定する
app.use(express.static(path.join(__dirname, "/public")));
// ejsテンプレートエンジンを設定
app.set("view engine", "ejs");
// トップ(フォーム)ページ
app.get("/", (req, res) => {
res.render("index");
});
// 入力結果を表示(result)
app.post("/result", (req, res) => {
const resultVal = req.body.inputval;
res.render("result", { resultVal });
});
// getリクエストの場合、トップにリダイレクト
app.get("/result", (req, res) => {
res.redirect("/");
});
// ポート3000で接続
app.listen(3000, () => {
console.log("running port 3000");
});
フォームを表示するテンプレートindexでは、共通テンプレートhead、header、footerをinclude()で読み込んでいます。
/views/index.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<%- include("./head"); %>
<title>form</title>
</head>
<body>
<%- include("./header"); %>
<main>
<form action="/result" method="POST">
<input type="text" name="inputval" />
<button type="submit">送信する</button>
</form>
</main>
<%- include("./footer"); %>
</body>
</html>
フォームで送信した値を表示する
フォームで送信された値はテンプレートresultで受け取り、ブラウザに表示させます。
/views/result.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<%- include("./head"); %>
<title>form</title>
</head>
<body>
<%- include("./header"); %>
<main>
<h1>入力結果</h1>
<p>あなたが入力したのは「<%= resultVal %>」です。</p>
</main>
<%- include("./footer"); %>
</body>
</html>
まとめ
今回、テンプレートエンジンについて調べました。
AxiosなどでHTTP通信でデータを取得できれば基本的なアプリが作れそうです。
外部データの取得については、別の機会にまとめたいと思います!