carlitoplatanito / gulp-nunjucks-render

[Gulp](https://github.com/wearefractal/gulp) plugin to render [Nunjucks](http://mozilla.github.io/nunjucks/) templates
149 stars 33 forks source link

How to pass Vinyl file data to templates? #64

Closed janwerkhoven closed 6 years ago

janwerkhoven commented 7 years ago

How does one pass Vinyl file objects to the templates?

// layout.nunjucks

{{file.path}}
{{file.dirname}}
{{file.relative}}

When building navigation menus, internationalising pages and doing SEO, I've often found myself in need for the page to know it's own path. How to do it? I've been breaking my head over this, but can't figure it out.

The closest I've come is to use Gulp Tap which outputs file data, but I haven't been able to pass this as data to nunjucksrender.

gulp.task('compileHtml', () => {
  return gulp.src(`src/templates/pages/**/*.+(html|nunjucks)`)
    .pipe(tap(function(file, t) {
      util.log(file.dirname.toString())
    }))
    .pipe(nunjucksRender({
      path: ['src/templates'],
      data: {
        data: myData
      }
    }))
    .pipe(gulp.dest(`dist/`))
});

Any ideas?

kristijanhusak commented 7 years ago

Where is myData defined? I think you do not need any additional plugins, just get the file information before returning gulp.src, and pass that as data.

janwerkhoven commented 7 years ago

So myData is defined earlier in the Gulpfile and static for all templates:

const myData = {
  projectName: 'This Project Name',
  environment: util.env.env || 'development',
  googleAnalyticsID: 'UA-12345678-XX',
  sitemapRootUrl: 'https://www.thisproject.com'
};

This allows templates to embed static data + environment logic:

<h1>{{data.projectName}}</h1>
{% if data.environment == 'production' %}
  <meta name="robots" content="index, follow">
{% else %}
  <meta name="robots" content="noindex, nofollow">
{% endif %}

Which is cool, but this information does not change per file I create. Is there a way to pass to each template it's Vinyl file data?

janwerkhoven commented 7 years ago

One example where it would be useful:

In navigation menus the active link should get a class active. Could do this by having it check it's own file.dirname and match it to the link.url.

data = {
  links: [
    { label: 'Homepage', url: '/' },
    { label: 'Products', url: '/products' },
    { label: 'Features', url: '/features' },
    { label: 'Contact', url: '/contact' }
  ]
}
<nav>
  <ul>
    {% for link in data.links %}
      <li>
        <a href="{{link.url}}">{{link.label}}</a>
      </li>
    {% endfor %}
  </ul>
</nav>

Could use something like this??

<li class="{{ 'active' if link.url === file.dirname }}">
revelt commented 7 years ago

@janwerkhoven well your idea with gulp.tap is fine and you're very close. In short, what you need is to tap the vinyl variables, dump them into some object somewhere higher in the variable scope, then use gulp-data to inject the custom objects into Nunjucks renderer through customising Nunjucks environment variable. Sounds complex but it's not, see my patched up piece below.

I did a test on my code and managed to add filePath and fileRelative (fileDirname was null in my case because the file was sitting in the root) into the Nunjucks Environment object. I can use {{ filePath }} and others anywhere in the template now.

Here's my patched up version of your code, I could not test it, but snippets should be correct as they've been taken from my working sample:

// declare a global var where we'll dump the `file.*` stuff:
var vinylInfo = {}

// use this to manage the data:
const data = require('gulp-data')

// data importer function
function getDataForFile (file) {
  var finalData = {}
  // start merging in the data:
  finalData = Object.assign(finalData, vinylInfo)
  // you can add more objects into your global object: SCSS vars, settings or whatnot.
  // all these vars will be available within Nunjucks templates using `{{ varName }}`.
  // just Object.assign them here.
  // Feel free to read JSON files from the filesystem and merge them in.
  // Tip. If you need advanced merging where maximum data is retained when there are data clashes,
  // check out https://github.com/codsen/object-merge-advanced from yours truly.

  return finalData
}

gulp.task('compileHtml', () => {
  // that's an ES6 arrow function, we create a variable named `manageEnvironment` which is equal to a function.
  var manageEnvironment = environment => {
    // add Nunjucks filters here
    // for example removal of "px" from the variable within Nunjucks:
    // environment.addFilter('removepx', str => str && str.replace(/px/g, '', str))

    // Turning off the HTML escaping, so we can put HTML in the content.
    // Saves us from hassle escaping all variables in templates.
    environment.opts.autoescape = false

    // Automatically remove leading whitespace from a block/tag:
    environment.opts.lstripBlocks = true  
  }

  return gulp.src(`src/templates/pages/**/*.+(html|nunjucks)`)
    .pipe(tap(file => {
      vinylInfo.filePath = file.path
      vinylInfo.fileDirname = file.dirname
      vinylInfo.fileRelative = file.relative
    }))
    .pipe(data(getDataForFile))
    .pipe(nunjucksRender({
      path: ['src/templates'],
      manageEnv: manageEnvironment,
      envOptions: {
        throwOnUndefined: false
      }
    }))
    .pipe(gulp.dest(`dist/`))
})

Managing the Nunjucks environment is slightly confusing because you have to set some things one way (manageEnvironment function) and some thing other way (inline via envOptions key). It's all in the documentation(s), but many people mix up the two and get frustrated it does not work. I added some most common settings above for you, just in case you need them in the future.

PS. Check out https://standardjs.com/, you probably don't need semicolons unless your team insists on using them.

TheDancingCode commented 6 years ago

This can easily be achieved by using gulp-data:

const data = require('gulp-data');

gulp.task('compileHtml', () => {
  return gulp.src(`src/templates/pages/**/*.+(html|nunjucks)`)
    .pipe(data(function(file) {
        return {
          file: {
            path: file.path,
            dirname: file.dirname,
            relative: file.relative
          }
        };
      }))
    .pipe(nunjucksRender({
        path: ['src/templates'],
        data: {
          data: myData
        }
      }))
    .pipe(gulp.dest(`dist/`));
});

And now, as desired, you can do this:

{{ file.path }}
{{ file.dirname }}
{{ file.relative }}