jonschlinkert / templates

System for creating and managing view collections, rendering, engines, routes and more. See the "dev" branch for most recent updates.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Removed `view` property from page context

dtothefp opened this issue · comments

@doowb @jonschlinkert
I have some template tags in Nunjucks that rely data from the view property such as path and key. It looks like with templates 0.14.6 this may have potentially been removed. Any chance to get this back or should I deal with it in some middleware?

templates 0.13.7 => assemble-core 0.13.0

{ 
  ext: '.html',
  engineName: undefined,
  settings: {},
  view: <File "index.html" <Buffer 7b 25 20 65 78 74 65 6e 64 73 20 6c 61 79 6f 75 74 73 28 27 68 6f 6d 65 27 29 20 25 7d 0a 0a 7b 25 20 64 65 62 75 67 20 25 7d 0a 7b 25 20 62 6c 6f 63 ... >>,
  async: true,
  helpers:
   { view: [Function: wrapped],
     page: [Function: wrapped],
     pages: [Function: wrapped] 
   } 
}

templates 0.14.6 => assemble-core 0.13.2

{ ext: '.html',
  async: true,
  helpers:
   { view: [Function: wrapped],
     page: [Function: wrapped],
     pages: [Function: wrapped] 
   },
  engineName: undefined
}

Setup

https://github.com/dtothefp/build-boiler/blob/master/packages/boiler-addon-assemble-nunjucks/src/index.js#L11

  const ext = '.html';
  const nunj = nunjucks.configure({
    watch: false,
    noCache: true
  });

  app.engine(ext, consolidate.nunjucks);

https://github.com/dtothefp/build-boiler/blob/master/packages/boiler-addon-assemble-nunjucks/src/tags/get-asset.js#L86

//tempalte tag
export default class GetAsset {
  constructor(app) {
    this.app = app;
    this.tags = ['get_asset'];
  }

  parse(parser, nodes, lexer) {
    const tok = parser.nextToken();
    const args = parser.parseSignature(null, true);

    if (args.children.length === 0) {
      args.addChild(new nodes.Literal(0, 0, ''));
    }

    parser.advanceAfterBlockEnd(tok.value);
    return new nodes.CallExtension(this, 'run', args);
  }
  run(context, args) {
    const {ctx} = context;
    const {
      assets = {}, //from app.data => assets = {'some/page/path': '/some/src.js'}
      environment,
      view
    } = ctx;  //ctx is page context
    const {isDev} = environment;
    const {path: viewPath} = view;  //breaks here because there is no `view.path`

    return new nunjucks.runtime.SafeString(`<script src="${assets[viewPath]}"></script>`);
  }
}

https://github.com/dtothefp/build-boiler/blob/master/src/templates/layouts/default.html#L24

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>

  {% get_asset %}
</body>
</html>

hmm, strange. view should definitely be there. that's not something I would change without advance notice, then a minor bump and a big "heads up" in the changelog.

as far as I know, nothing has been removed from the context since templates was created. latest is 0.15.10, have you tried that version?

@jonschlinkert yes I tried templates 0.15.10 with assemble-core 0.14.3, still the same problem. can try to make you a small example

actually, I'm pretty sure view was never directly on the helper context (e.g. this.view) until recently. it was always on this.context.view. Now it's on both.

however... this.context.view is non-enumerable (I'm pretty sure it always was, but it's possible it was enumerable before, in which case this would be a regression).

I just now remembered something @doowb mentioning something about view, that might have been related. @doowb, maybe you remember?

I think I just realized what happened....

Before we returned the context object from bindHelpers and it was the same object that was used as the thisArg for helpers. Now, thisArg is different from the context that we're returning from bindHelpers.

I think we either need to add the view to the returned context (like in .compile) or add it to this context object

@dtothefp is having an issue because the context object passed into those nunjucks tags is the context that we pass into the render function... engine.render(view.contents, context). So it just needs to current view to be added.

I just now remembered something @doowb mentioning something about view, that might have been related. @doowb, maybe you remember?

This was a different issue related to the current view, but that was inside handlebars helpers and has been fixed.

Before we returned the context object from bindHelpers

ahhh, damn. good insight @doowb. surprising that this isn't covered in like 1,200 unit tests

however... this.context.view is non-enumerable (I'm pretty sure it always was, but it's possible it was enumerable before, in which case this would be a regression).

This is what's happening.

The .view property is on the context just like before (I missed where the reference was still kept throughout the bindHelpers method.

The issue is that it looks like nunjucks doesn't keep non-enumerable properties on the context. When I do this.context.view = view in the Context object, I see the view in the nunjucks tag, but I'm not able to even get to the view when doing ctx.view in the tag, so it must be removing it.

I even tried adding the view to the context passed in when rendering and it didn't show up. Nunjucks must bind the context from the compile function and only use that (unless there's something else I don't know about nunjucks, which is probably the case)

if you change it to this.context.view instead of using define did it work?

if you change it to this.context.view instead of using define did it work?

Yes

oh yeah sorry I see where you said that already. thx

@doowb @jonschlinkert hey guys so what does this all mean? I don't want you to have to change your code just to accommodate nunjucks. Is this something I just shouldn't count on in the future and just bind the view context I need in an onload middleware or something?

hey guys so what does this all mean?

Nunjucks only "sees" enumerable properties when they make a copy of the context passed in. Since we're doing something like...

Object.defineProperty(context, 'view', {
  configurable: true,
  enumerable: false,
  value: view
});

...nunjucks doesn't "see" the view property and doesn't copy it over to the context.ctx property in your tag.

just bind the view context I need in an onload middleware or something?

The following using preCompile works for nunjucks.

app.create('pages', {renameKey})
  .use(loader())
  .preCompile(/./, function(file, next) {
    file.data.view = file;
    next();
  });

This lets you use whatever property you want and put other things on the file.data object that will be accessible in your tags.

As @jonschlinkert said above...

however... this.context.view is non-enumerable (I'm pretty sure it always was, but it's possible it was enumerable before, in which case this would be a regression).

...so maybe we should make it non-enumerable.

FWIW... I found this issue on nunjucks that talks about es2105 getter syntax not working (which I think transpiles into Object.defineProperty). Looks like they'd be willing to take a PR if you find a fix 😉

...so maybe we should make it non-enumerable.

@doowb meant "make it enumerable". yeah let's do that. I haven't looked at the nunjucks internals, but I imagine they'll need to do more than a simple object-assign on the context. getters should also be copied

I don't want you to have to change your code just to accommodate nunjucks.

meant to reply to this. if view was enumerable before, the fact that behavior has changed for you makes this a regression. we'll make view enumerable, but in the meantime hopefully you are able to solve it for now using the solution @doowb posted

closing since I think this was resolved a while ago. please feel free to reopen if there is still an issue