1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ただ値を埋め込むだけの簡単なテンプレートエンジンをNode.jsで自作する

Last updated at Posted at 2019-12-16

JavaScriptで Infrastructure as Code のツールを開発しています

以前こちらの記事で、サーバからShellScriptで取得してきた値を、HTML表示する機能を紹介しましたが、その際にHTMLに値を埋め込む簡単なテンプレートエンジンを自前で実装したので、その紹介です

テンプレートエンジンとは?

Wikipediaのページが、ちゃんとあるような専門用語なんですね

固い説明はそちらに任せるとして、ものすごく乱暴に説明すると、 hoge='fuga' のとき I am a {{ hoge }}. という文字列の {{ hoge }} 部分に I am a fuga. というように 'fuga' を埋め込んでくれるようなもののことです

私はAnsibleを本職でよく利用するのでPythonの世界で有名なテンプレートエンジンである Jinja2 の経験があります

実際のテンプレートエンジンでは {{ 2 + 3 }}5 というように四則演算ができたり、テンプレートの中に関数を埋め込んだりできるのですが、この記事で紹介するのは、単に変数の値を埋め込む機能だけの簡単なものになります

npmで適当なテンプレートエンジン提供されてないの?

Node.jsでもいくつかテンプレートエンジンはあるようですが、今回それらを使いませんでした。理由は2つあります

  1. 調べた限りでもテンプレートエンジンありすぎて、どれが良いのかよく分からない
  2. 可能な限り依存パッケージは減らしたい (というかクライアントサイドでVue.js使った以外はnpmの依存パッケージは0にしました)

どうやって実装したのか

おおまかな処理の流れは、以下のような関数にまとめられます

  1. テンプレートファイルを文字列としてロードする
  2. 1を改行コード /\r\n|\r|\n/ (正規表現) で分割する
  3. 2を1要素(1行)ごとに取り出し、値の埋め込み部分を表す {{%%}} で分割する
  4. 3の奇数番目の文字列({{%%}} で囲まれた文字列)の両端のスペース を取り除く(トリミング)
  5. 4の結果が引数として渡されたオブジェクトのキーとして存在する場合のみ、値を置換する
  6. 全体を 改行コード \n でつなげる

※テンプレート内に改行を含む場合は対応しませんでした
※また、別のテンプレートエンジンで使われそうな {{ }} と見分けるために、あえて {{% %}} にしています

コードとしては以下のようになります

(1) テンプレートファイルを文字列としてロードする

TemplateEngine.js
// ファイルアクセスのモジュールをロード
const fs=require('fs');

// テンプレートファイルを文字列としてロードする
// テンプレートは関数の外で1回ロードすれば充分
const template=fs.readFileSync(
  `${__dirname}/templates/hoge.templ`
).toString();

(2) (1)を改行コード /\r\n|\r|\n/ (正規表現) で分割する

TemplateEngine.js
const render=(props)=>{
  return template
    .split(/\r|\n|\r\n/)
}

(3) (2)を1要素(1行)ごとに取り出し、値の埋め込み部分を表す {{%%}} で分割する

TemplateEngine.js
const render=(props)=>{
  return template
    .split(/\r|\n|\r\n/)
  .map(
    line => line.split(/{{%|%}}/);
  );
}

(4) (3)の奇数番目の文字列の両端のスペース を取り除く(トリミング)

TemplateEngine.js
const render=(props)=>{
  return template
    .split(/\r|\n|\r\n/)
  .map(
    line => {
      const words=line.split(/{{%|%}}/);

      return 1 < words.length
        ? words.map(
            (w, i) => 0 < i%2
              ? w.replace(/^ */, '')
                 .replace(/ *$/, '')
              : w
          ).join('')
        : line
    }
  );
}
  1. 上記の処理をまとめ、必要な部分に値を埋め込む関数
TemplateEngine.js
// ファイルアクセスのモジュールをロード
const fs=require('fs');

// テンプレートファイルを文字列としてロードする
// テンプレートは関数の外で1回ロードすれば充分
const template=fs.readFileSync(
  `${__dirname}/templates/hoge.templ`
).toString();


const render=(props)=>{
  return template
    .split(/\r|\n|\r\n/)
  .map(
    line => {
      const words=line.split(/{{%|%}}/);

      return 1 < words.length
        ? words.map(
            (w, i) => 0 < i%2
              ? props[
                  w.replace(/^ */, '')
                   .replace(/ *$/, '')
                ]
              : w
          ).join('')
        : line
    }
  ).join('\n');
}

テンプレートを用意して実行するとこんな感じ

hoge.templ

I am a {{% hoge %}}.

TemplateEngine.js
console.log(
  render({ hoge: 'fuga', })
);
$ node TemplateEngine.js

I am a fuga.

おわりに

ただ値を埋め込むだけなら、とても簡単に実装できることが分かります

これなら余計なパッケージのバージョン管理やnodeのバージョンとの互換を気にする必要がなくなるので、独自実装の方が長い目で見ると楽かもしれません

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?