JavaScript: Gulp4 + EJS のインクルードファイルの相対パスを変数化する

EJS を使用し、header や footer 等の共通部分を外部から呼び出す場合、インクルード元のファイルの階層が違うと、相対パスのリンクが切れてしまいます。
今回は、あらゆる階層から呼び出されても階層にあわせて相対パスが変わるように、EJS ファイルに変数を割り当てる方法を説明したいと思います。

目次

  • Gulp4 + EJS のインクルードファイルの相対パスを変数化する
  • まとめ

Gulp4 + EJS のインクルードファイルの相対パスを変数化する

以下のような階層構造で、index.ejs と page.ejs がともに header.ejs をインクルードした場合のサンプルコードとなります。

ファイルツリー

project
├ gulpfile.js
├ pubric
│ ├ ...
│ └ ...
└ src
  └ ejs
    ├ _data
    │ └ site.json
    ├ _partial
    │ └ header.ejs
    ├ _pages
    │ └ page.ejs
    └ index.ejs

gulpfile.js

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

// paths
const paths = {
    src: 'src/',
    dest: 'public/',
    root: 'src/ejs/',
    data: 'src/ejs/data/'
}

// EJS
gulp.task('ejs', () => {
    var ts = Date.now();
    return gulp.src([paths.src + "ejs/**/*.ejs", '!' + paths.src + "ejs/**/_*.ejs"])
        .pipe(
            data(file => {
                const absolutePath = `/${file.path
          .split(paths.root)
          [file.path.split(paths.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(`${paths.data}site.json`)),
            }),
        )
        .pipe(ejs({}, {}, {
            ext: '.html'
        }))
        .pipe(replace(/__NOCACHE__/g, ts)) //replace __NOCACHE__
        .pipe(plumber({
            errorHandler: notify.onError('Error: <%= error.message %>')
        }))
        .pipe(gulp.dest(paths.dest));
});

site.json

サイト全体で使用するデータです。主に head タグ内で利用するものを記述しています。
(例)<%= site.name %> で “Your Sitename” が出力

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

header.ejs

<%
var page = {
  absolutePath: absolutePath,
  relativePath: relativePath
};
-%>
<nav class="navbar navbar-expand-md">
  <a class="navbar-brand" href="<%= page.relativePath %>">brand</a>
  <div id="navbarsDefault" class="collapse navbar-collapse">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" href="<%= page.relativePath %>_pages/xxx.html">リンク1</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="<%= page.relativePath %>_pages/xxx.html">リンク2</a>
      </li>
    </ul>
  </div>
</nav>

index.ejs

<%
var page = {
  title: "index",
  description: "",
  absolutePath: absolutePath,
  relativePath: relativePath
};
-%>

<!DOCTYPE html>
<html>
<head>
  <title><%= site.name %>|<%= page.title %></title>
  <meta charset="utf-8">
  <meta name="description" content="<%= site.description %>">
  <meta name="author" content="">
</head>
<body>
  <!-- Content -->
  <div id="app">
    <!-- nav.navbar start -->
    <%- include(page.relativePath + '_partial/header', {page: page}); %>
    <!-- nav.navbar end -->

    <div class="container">
        <h2>index</h2>
    </div>
  </div>
</body>

page.ejs

<%
var page = {
  title: "page",
  description: "",
  absolutePath: absolutePath,
  relativePath: relativePath
};
-%>

<!DOCTYPE html>
<html>
<head>
  <title><%= site.name %>|<%= page.title %></title>
  <meta charset="utf-8">
  <meta name="description" content="<%= site.description %>">
  <meta name="author" content="">
</head>
<body>
  <!-- Content -->
  <div id="app">
    <!-- nav.navbar start -->
    <%- include(page.relativePath + '_partial/header', {page: page}); %>
    <!-- nav.navbar end -->

    <div class="container">
        <h2>index</h2>
    </div>
  </div>
</body>

まとめ

EJS を導入する以上、目的は静的ファイルの大量生成となるはずなので、相対パスの変数化は必須です。
gulpfile.js さえコーディングしてしまえば次回以降使い回しがきくので、今回説明した作業はしっかりとおさえておきたいところです。

この記事がみなさんのお役に立ちましたら、下記「Share it」よりブックマークやSNSで共有していただければ幸いです。

siro:chro 無料ゲーム SQN をリリースしました

img_sqn_00

sirochro 初の無料ゲームアプリ SQN をリリースしました。
記事:SQN: iOS 無料ゲームアプリ SQN - Sequential Numbers をリリース
ちょっとした時間に楽しめる完全無料のゲームなっていますので、是非ダウンロードして遊んでみてください。

↓SQN のダウンロードはこちらから

Related Contents

Pickup Contents