4

I understand that Pug does not support dynamic includes or extends in templates. Ie

extend path/to/template 

works but not

extend #{dynamic_path_to_template}

Is there a workaround (however convoluted) that will allow the same goal of modifying the template used by a view at runtime

Context: My use case is that I am developing an npm module and the template being used to extend other views is located inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested.

takinola
  • 1,643
  • 1
  • 12
  • 23

5 Answers5

2

I had this issue aswell and found this question while searching for a solution. My solution is similar to Nikolay Schambergs Answer, but i thought i should share it.

I've created a function that renders templates by giving it a path and passed it to the options object. Maybe it helps in your case aswell

const includeFunc = (pathToPug, options = {}) => {
    return pug.renderFile(pathToPug, options); //render the pug file
}

const html = pug.renderFile('template.pug', {include: includeFunc});

and then use it as followed in your template:

body
  h1 Hello World
  |!{include(dynamicPugFilePathFromVariable)}
1

There is no way to do this for now, but you can work out your application architecture without dynamic extends.

Possible solution #1

  1. Make a layout.jade that conditionally include multiple layouts:

    layout.jade:
    
    if conditionalVariable
        include firstLayout.jade
    else
        include otherLayout
    
  2. In your view, extend layout.jade, and define conditionalVariable in the controller (true/false):

    view.jade:
    
    extends layout
    
    block content
        p here goes my content!
    

Possible solution #2

  1. Pass configurations to the layout

    - var lang = req.getLocale();
    doctype html
       block modifyLayout
    
  2. split the project into multiple entrances, each entrance extends the layout and passes its different configs, and includes different things in different blocks

    extends ../layout
    block modifyLayout
       - var lang = "en" //force language to be en in this page.
    block body
       include my-page-body
    

Possible solution #3

use something like terraform which uses pug as its rendering engine, but it enables you to use dynamic partials like this

!= partial(dynamicFileFromVariable)
Bamieh
  • 10,358
  • 4
  • 31
  • 52
  • Solution #1 & #2 look good except they require knowledge of the location of the possible template files. My use case is that I am developing an npm module and the template is inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested. – takinola Aug 22 '17 at 21:10
  • before running the pug renderer, you can write to file system a pug file with the dynamic path that your environment knows its location. – Bamieh Aug 22 '17 at 21:17
  • How does that work? Are you suggesting doing something like "fs.writeFile(/known/location/my_template.pug)" inside an initialization sequence and then using the newly created pug file template to extend my views? If so, I thought Pug requires all the files to be available immediately at runtime since everything is loaded into memory. – takinola Aug 22 '17 at 21:22
1

In order to do dynamic include, you will have to use Unescaped String Interpolation, inserting pug contents that are pre-compiled before your main .pug file inside your route. In other words it works as follows:

1) Some .pug files are pre-compiled into HTML 2) The HTML gets fed into another .pug file compilation process

Here's an example how to do it

Inside your router file (routes.js or whatever)

var pug = require('pug')
var html = []
var files = ['file1','file2'] // file names in your views folders
let dir = path.resolve(path.dirname(require.main.filename) + `/app/server/views/`)
//dir is the folder with your templates
app.get('/some-route', (req,res) => {
    for (let n = 0; n < files.length; n++) {

        let file = path.resolve(dir + '/' + files[n] + `.pug`)
        fs.access(file, fs.constants.F_OK, (err) => {
            if (!err) {
                html.push(pug.renderFile(file, data))
                if (n === files.length - 1) {
                    res.render('dashboard', {html})
                }
            }
            else {
                res.status(500).json({code:500,status:"error", error:"system-error"})
            }
        })
    }
})

Inside your desired .pug file:

for item in html
    .
        !{item}

The example above is specific to my own use case, but it should be easy enough to adapt it.

1

It works!

First, set res.locals middleware.

middlewares/setResLocals.js

const pug = require('pug')
const path = require('path')
module.exports = function(req, res, next) {
  res.locals.include = (pathToPug, options = {}) => { // used for imitate includ pug function
    return pug.renderFile(pathToPug, options); //render the pug file
  }
  res.locals.__PATH__ = path.join(__dirname, '../')
  next()
}

server/index.js

app.use(require('../middlewares/setResLocals'))

file.pug

|!{include(`${__PATH__}/${something}`)}
Wiki HSK
  • 19
  • 1
  • 1
0

I know, this is a bit late for answering. But I found a possibility suitable for my purpose by this bit of information from the pug docs:

If the path is absolute (e.g., include /root.pug), it is resolved by prepending options.basedir. Otherwise, paths are resolved relative to the current file being compiled.

So, I provide most of my pug modules by relative paths and the stuff I want to exchange dynamically is organised in pug files of the same name but in different folders (think theme) and include them by absolute paths . Then I change the basedir option to dynamically choose a set of pug files (like choosing the theme).

May this help others, too.

Captain Fim
  • 524
  • 2
  • 13