EJSでルートへのパス（相対、絶対）を取得して変数化する

※ 2019年5月19日 EJSで`<%= filename %>`でパスの取得ができなくなっているので、gulp-dataを使って、gulpfile.js内で取得する方法に変更しました。

[EJS](http://ejs.co/)ではインクルードするパスを相対パスで指定する必要があります。設定ミスを防ぐために、メタタグ内のURLなども自動化したいところです。

開発環境はmacOS High Sierra 10.13.6、Node.js 9.4.0、gulp-ejs 4.0.0で確認しています。

## gulpfile

```js
const ejs = require('gulp-ejs');
const fs = require('fs');
const data = require('gulp-data');
const rename = require('gulp-rename');
const plumber = require('gulp-plumber');
const notify = require('gulp-notify');

/**
 * 開発用ディレクトリ
 */
const src = {
  root: 'src/',
  ejs: ['src/**/*.ejs', '!src/**/_*.ejs'],
  data: 'src/_data/',
};

/**
 * テスト用ディレクトリ
 */
const dest = {
  root: 'htdocs/',
};

/**
 * EJSをHTMLにコンパイルします。
 */
gulp.task('ejs', () => {
  return gulp
    .src(src.ejs)
    .pipe(
      data(file => {
        const absolutePath = `/${file.path
          .split(src.root)
          [file.path.split(src.root).length - 1].replace('.ejs', '.html')
          .replace(/index\.html$/, '')}`;
        const relativePath = '../'.repeat([absolutePath.split('/').length - 2]);
        return {
          absolutePath,
          relativePath,
        };
      }),
    )
    .pipe(
      ejs({
        site: JSON.parse(fs.readFileSync(`${src.data}site.json`)),
      }),
    )
    .pipe(rename({ extname: '.html' }))
    .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
    .pipe(gulp.dest(dest.root));
});
```

## `site`変数
サイト共通で使う変数を`/src/_data/site.json`に定義します。この変数はgulpfile.jsを介して、すべてのEJSファイル内で`site.name`のように呼び出すことができます。

```js
{
  "name": "Site Title",
  "description": "Site Description",
  "keywords": "Site Keyword1, Site Keyword2",
  "rootUrl": "http://example.com",
  "ogpImage": "http://example.com/ogp-image.jpg",
  "facebookAdmins": "",
  "facebookAppId": "",
  "twitterCard": "summary_large_image",
  "twitterSite": "@SiteAccount"
}
```

製品リストのような別の変数を定義したい場合は、jsonファイル（`/src/_data/products.json`）を作成して、gulpfile.jsで以下のように読み込んでください。

```js
// 変更前
.pipe(
  ejs(
    {
      site: JSON.parse(fs.readFileSync(`${src.data}site.json`)),
    },
    {},
    { ext: '.html' },
  ),
)
```

`<%= products.items %>`のように呼び出すことができます。

```js
// 変更後
.pipe(
  ejs(
    {
      site: JSON.parse(fs.readFileSync(`${src.data}site.json`)),
      products: JSON.parse(fs.readFileSync(`${src.data}products.json`)),
    },
    {},
    { ext: '.html' },
  ),
)
```

## `absolutePath`と`relativePath`
gulpfile.jsで`fs`パッケージを使用して、そのページのルート相対パスを`absolutePath`に、ルートまでの相対パスを`relativePath`に格納しています。この変数は、すべてのEJSファイル内で`<%= absolutePath %>`のように呼び出せます。  
`index.html`は削除され、以下のように出力されます。

- `/index.ejs`で使用した場合
  - `<%= absolutePath %>` => `/`
  - `<%= relativePath %>` => 出力なし
- `/products/index.ejs`で使用した場合
  - `<%= absolutePath %>` => `/products/`
  - `<%= relativePath %>` => `../`
- `/products/product.ejs`で使用した場合
  - `<%= absolutePath %>` => `/products/product.html`
  - `<%= relativePath %>` => `../`

`site.rootUrl`と組み合わせることで、ドメインを含んだ絶対パスを出力することもできます。

```js
<%= site.rootUrl %><%= absolutePath %>
```

## メタタグの自動出力
共通のデータやルート相対パスなどは出力できるようになったので、メタタグも自動で生成することができます。

例えば、以下のようにindex.ejsを作成します。`include()`の第二引数に`page`変数を渡して、ページごとの設定をインクルード先（`src/_partial/_head.ejs`）で使えるようにしておきます。

```js
<%
var page = {
  title: "",
  description: "",
  ogpType: "website",
  ogpImage: "",
  // Do not change.
  absolutePath: absolutePath,
  relativePath: relativePath
};
-%>
<%- include(page.relativePath + '_partial/_head', {page: page}); %>
```

_head.ejsでは以下のように、`page.title`を設定していればページタイトルとサイトタイトルを、`page.title`がなければサイトタイトルを生成のように、テンプレートを作れます。

```js
<% if(page.title) { %><title><%= page.title %> | <%= site.name %></title><% } else { %><title><%= site.name %></title><% } %>
```