FatalKeystroke
FatalKeystroke

Reputation: 3062

EJS include file relative to project root

I am using Express and EJS to build a site and I have a directory structure somthing like:

+--www/
  |
  +--partials/
  | |
  | +--header.ejs
  | +--(a bunch of ejs files)
  |
  +--guide
  | |
  | +--index.html
  | +--(other files)
  |
  +--index.html

In both of the index.html files shown in my example, the <% include ... %> command would be different, even if referencing the same included file.

Also if I were to say include header.ejs and then header.ejs has an include for another partial, the whole system breaks down because they are all looking for files in different locations.

To make management easier, I'm trying to find a single string to be able to reference the same included files regardless of what sub-directory the files may be in.

Ideally something like <% include /partials/header.ejs %> would be perfect. But that doesn't work.

Does anyone have any tricks or advice that could give the desired result here?

Upvotes: 18

Views: 15932

Answers (6)

Jim
Jim

Reputation: 675

I would recommend adding the following to your express file:

const includePath = path.resolve('<include dir here>');

Then adding it to your renders:

res.render('example', { val1: 1, val2: 'two', includePath });

And in the files:

include(includePath + '/include.ejs');

Upvotes: 0

Rehners
Rehners

Reputation: 11

It has been 6 years since this question was asked... but I'd like to post a possible solution for now (2022).

Check the typescript declaration of EJS, I found that it now provides an option named includer wich is a function that allow you to adjust the file path. If all your including files are loacted in includeRoot, then you may write something like this:

ejs.render(htmlString, data, {
    includer: file => {
        // `file` is the string passed to include
        let modifiedFilename = path.resolve(includeRoot, file)
        return { filename: modifiedFilename }
    }
})

Upvotes: 1

babakfp
babakfp

Reputation: 334

(Other solutions looked a little complicated, so I decided to send my solution too). I tested it on Heroku and it worked fine.

  1. Create app-locals.js and write this inside it:
module.exports = {
  layouts:  process.cwd() + '/views/layouts/',
  partials: process.cwd() + '/views/partials/',
}
  1. In app.js:
// const express = require('express')
// const appLocals = require('./app-locals.js')

// const app = express()
app.locals = appLocals
  1. Use it like:
<%- include(partials + 'header.ejs') %>

Or you can do it like this:

// app-locals.js
module.exports = {
    // Remove the last /
  layouts:  process.cwd() + '/views/layouts',
  partials: process.cwd() + '/views/partials',
}

// app.js
// Add / after the path
<%- include(`${partials}/header.ejs`) %>
  • If you are using this on Heroku, make sure that your files and folder names all are lowercase and that you are using the same case when including the files.

Upvotes: 6

Mohamed Jakkariya
Mohamed Jakkariya

Reputation: 1657

In 2021, October

I'm also having the same issue with importing files on ejs. But I used to figure it out the following way to include my other ejs partials from the parent directory.

Pros

  1. Don't need to set up view engine and directory path on server file.
  2. Independent directory names and locations we can achieve.

Working file: src/views/en/confirmation.ejs

Parent file: src/views/common/footer.ejs (Needs to included on each ejs file)

In src/views/en/confirmation.ejs`

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
     <body>
         <h1>this is body</h1>
     </body>
     
     <% var dir = process.cwd() %>
     <%- include(dir + "/src/views/common/footer.ejs") %>
  
</html>

Related: Error: Could not find the include file "partials/head"

Upvotes: 2

MER
MER

Reputation: 1561

This is a very good question, it's one I had myself until very recently.

Really there appears to be no direct & central way to get the web root... it's just not something the node nor the express developers thought was valuable it seems.

However there are a few tools made available that can help but won't directly solve the problem.

GENERAL APPROACH:

  1. Get the root from the initial file (where express is instantiated)
  2. Put that into a variable
  3. Get that variable out to your templates

METHOD ONE - globals
The first work around I found is passable. It utilizes the ability to set a variable to the global scope. It's not ideal but if you want to see more click HERE.

IMPORTANT: All of the below require that in the main file of your express app (the one that instantiates the express app) you set a variable to the current working directory.
The three ways I know of are

  1. __directory
  2. path.dirname(require.main.filename);
  3. path.resolve();

I believe there is an advantage to the second method but I don't remember what it is currently... also I'm not sure it works in the latest (14.x) version of node... I started changing over to 3 once I started using 14.5...

METHOD TWO - passing variables
The second workaround I think is the worst... it will work but may become untenable if you have anything less than a simple setup. This method is to pass the variables through your routes. this would look like:

router.get('/', (request, response) => 
{
  response.render('PATH TO TEMPLATE', {dataItem1: 'DATA STUFF TO PASS', dataItem2: 'MORE STUFF'});
});

METHOD THREE
This is definitely the method I prefer. It makes it easier, simpler, and more clean. There are really two methods here but they are very similar.

This method is utilizing either app.locals OR response.locals. When I say app I mean the instance of express, (whatever you called it). So this is as simple as:

  1. Creating a middleware to pass variables in response.locals
  2. Setting variables directly on app.locals
    Examples:
const appDir = path.dirname(require.main.filename);

app.use((request, response, next) => 
{ 
    response.locals.someVar = 'value of someVar'; 
    response.locals.appDir = appDir;
    return next();
});
  1. app.locals.appDir = path.dirname(require.main.filename);//const path = require('path');

In both cases the values are simply available in EJS templates.
So using them would look like:

<%= appDir %>

Upvotes: 3

karthikdivi
karthikdivi

Reputation: 3643

Looks like this is not supported by EJS however i found a workaround for this problem. In the above setup the pain point is for all partial files you need to mention the relative paths and if you refactor code that becomes more pain. So rather mentioning the relative path at every include i declare a variable rootPath once, and there i give the path to reach home. So that at every include i can simply mention the relative path just like path from root.

For example in guide/index.ejs i mention the following in top of the ejs file

<% var rootPath = '../'; %>

and and code in ejs file looks like below

<%- include(rootPath + 'partials/header'); %>

Your HTML code

<%- include(rootPath + 'partials/footer'); %>

So in case i refactor index.ejs to some other folder all i need to do is change the value of rootPath

Upvotes: 8

Related Questions