Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nunjucks 4 roadmap / tracking ticket #1059

Open
8 tasks
fdintino opened this issue Jan 28, 2018 · 25 comments
Open
8 tasks

Nunjucks 4 roadmap / tracking ticket #1059

fdintino opened this issue Jan 28, 2018 · 25 comments

Comments

@fdintino
Copy link
Collaborator

fdintino commented Jan 28, 2018

This issue is a draft and is still being updated

Features

Bug fixes

@ArmorDarks
Copy link

Fix nested macros and call/caller (#664, #799)

Oh my, if those will be fixed, I will be obligated to buy you a beer :)

Btw, are there any plans to open Patreon or any other way to donate to maintainers?

@jgerigmeyer
Copy link
Contributor

I'm guessing it relates pretty directly to #664 and #799, but #912 would be another killer bugfix to add to the list! 🍻

@atian25
Copy link
Contributor

atian25 commented Feb 23, 2018

plz also support promise render

@ArmorDarks
Copy link

@atian25 right now you can simply promisify render method. It takes few lines of code.

@atian25
Copy link
Contributor

atian25 commented Feb 23, 2018

yes, I had do it long time ago.

Just think about it's 2018 now, and maybe promise-base interface should be a built-in feature.

@cungminh2710
Copy link

Is a roadmap for making asynchronous calls inside macros possible ?

@fdintino
Copy link
Collaborator Author

@cungminh2710 could you elaborate a bit on what you mean?

@fdintino
Copy link
Collaborator Author

fdintino commented May 8, 2019

I'm finally making progress on this, and I'm encountering something that I wanted to solicit feedback on.

In short, many of these features are made much much easier to implement if we can use ES6 syntax in our compiler. As just one example, generators and for ... of greatly simplifies both the compiler code (i.e., the logic that generates the compiled template) and the resulting template js itself. The reason it's tricky to do this without those language features is because when you introduce the conditional loop syntax (e.g. {% for i in list if i % 2 == 1 %}) it is no longer sufficient to have the compiled code emit

frame.set("loop.index0", 1);
frame.set("loop.index", 2);
// ...

instead, ideally (and this is how jinja2 does it) loop should be an instance of a LoopContext that wraps an iterable passed into its constructor. As a concrete example, this template:

{% for item in list if item.index % 2 == 1 recursive %}
  {{ loop.index }}
  {% if item.children %}
    {{ loop(item.children) }}
  {% endif %}
{% endfor %}

would compile to this source (cleaned up and simplified for readability):

function root(env, context, frame, runtime, cb) {
  var lookup = runtime.memberLookup;
  function resolve(name) {
    return runtime.contextOrFrameLookup(context, frame, name);
  }
  function toStr(val) {
    return runtime.suppressValue(val, env.opts.autoescape);
  }
  function call(name, obj, args) {
    return runtime.callWrap(obj, name, context, args);
  }

  var output = "";

  try {
    var _list = resolve("list");

    function* wrapForIf(_list) {
      frame = frame.push();
      for (let _item of _list) {
        frame.set("item", _item);
        if (lookup(_item, 'index') % 2) {
          yield _item;
        }
      }
      frame = frame.pop();
    }

    function loop(iter, loopRenderFunc, depth = 0) {
      var innerOutput = "";
      var loopCtx = new runtime.LoopContext(wrapForIf(iter), loopRenderFunc, depth);

      for (let [_item, _loop] of loopCtx) {
        frame.set("item", _item);
        frame.set("loop", _loop);
        innerOutput += toStr(lookup(_item, "index"));
        if (lookup(_item, "children")) {
          innerOutput += toStr(call("loop", _loop, [lookup(_item, "children")]));
        }
      }
      return innerOutput;
    }

    output += loop(_list, loop);

    cb(null, output);
  } catch (e) {
    cb(runtime.handleError(e));
  }
}

Doing the above is possible with ES5, but it's an ugly, fragile mess. And it balloons the size of the library with a bunch of utilities and polyfills.

If I went this route, we could allow people to support old browsers with one or more of the following options:

  • Recommend that people precompile their templates and hook it into their existing build tooling
  • Provide built-in babel support to the precompile API for people who don't have js build tooling
  • In the event that someone wants to compile a template in the browser at runtime, allow them to pass a source transformer into the Environment, so that they could use something like @babel/standalone

Thoughts?

@SalathielGenese
Copy link

Seems good so far. I upvote

@ArmorDarks
Copy link

Well... Is it the end of the Nunjucks?

@fdintino
Copy link
Collaborator Author

fdintino commented Aug 6, 2019

@ArmorDarks what do you mean?

@fdintino
Copy link
Collaborator Author

fdintino commented Aug 6, 2019

I looked into the feasibility of requiring @babel/standalone to compile nunjucks templates in-browser and determined that it would not work—it's 380k minified + gzipped; I started a project called pregenerator that can transform generators (and do other es6 transforms) while only being 88k minified and gzipped. I'd like to get it down to 60 before I think it's fully ready for use.

@ArmorDarks
Copy link

Oh, sorry. I've had an impression that the repository wasn't updated for a while.

Though, I still wonder how people able to use Nunjucks with that series of sewer scope-related bugs (#664, #799, #912) which were introduced in v3. They make the building anything but very small projects impossible.

I remember there were some movements regarding them... but it was like a few years ago...

@fdintino
Copy link
Collaborator Author

fdintino commented Aug 6, 2019

Yup. I have a v4 branch I've been working on. Those scoping issues are addressed in it. It will hopefully be at a point soon where I can release it as a public beta.

@ArmorDarks
Copy link

Ah, great to hear! It would be so nice to finally update Kotsu, since it stuck with v2 due to those bugs...

@ArmorDarks
Copy link

I'm partially missing the point why you're attempting to compile ES6 code right in the browser. I've read the post above about simplicity ES6 provides, but why would we force to compile it right inside browsers?

Today any library can provide two versions — one is totally compiled and built (usually with larger footprint), and the other one is close to the sources — es6-version with exports/imports, which can be consumed by any other tool and built according to the requirements of the project (so they can opt to include more polyfills, or target only modern browsers and have smaller js-file).

@fdintino
Copy link
Collaborator Author

fdintino commented Aug 7, 2019

Right. This would remain an option, perhaps even the default. And nunjucks-slim would not require any transpilation in-browser—it will have already been done server-side. But because nunjucks generates javascript code, and plenty of people still use nunjucks on old browsers, I felt it necessary to provide an option for those people. We will be building an esm and a legacy browser js bundle. The es6 version of the library would not include the transpilation.

This was referenced Mar 29, 2020
@ogonkov
Copy link
Contributor

ogonkov commented Mar 29, 2020

@fdintino you are converting files to ES modules in your branch. It seems not break anything for 3.x branch since it's post-processed by Webpack/Babel, may be we could do it for current branch, to decrease diff of 4.x version? I could make PR.

@fdintino
Copy link
Collaborator Author

fdintino commented Apr 2, 2020

It would break some code, because it moves the files inside nunjucks to a different location. So if somebody were requiring something from nunjucks/src/environment.js (for instance) it would no longer work.

@ogonkov
Copy link
Contributor

ogonkov commented Apr 2, 2020

Without moving files

@fdintino
Copy link
Collaborator Author

fdintino commented Apr 2, 2020

Hmm, yes I suppose the change to rollup and ESM can be separated from the monorepo structure changes.

@dfischer0
Copy link

@fdintino any update on Nunjucks 4? Is there any chance it will come out one day?
Also, @cungminh2710 asked a while back about asynchronous support in macros and I believe you didn't get what he meant. He meant support for anything asynchronous inside a macro - such as an async filter. Right now there's a note in the documentation stating:

Important note: If you are using the asynchronous API, please be aware that you cannot do anything asynchronous inside macros. This is because macros are called like normal functions. In the future we may have a way to call a function asynchronously. If you do this now, the behavior is undefined.

I am super interested in this feature (and I have created #1320 for this.

@fdintino
Copy link
Collaborator Author

fdintino commented Oct 9, 2020

Hi @dfischer0 . I'm still making progress on nunjucks v4, but I now have a 5 month old daughter at home, which has sucked up some of the time I would otherwise be spending on this project.

The first step was to write a proof-of-concept transpiler that could convert newer ES features to ES5, but most importantly generators and async/await, while being smaller than either babel-standalone or regenerator. The result of that was pregenerator, which supports most ESNext features and has min+gz size of 88K, compared to 362K for babel-standalone and 309K for regenerator.

This was to test the feasibility of bundling a transpiler with the full browser nunjucks package, so that folks could compile templates in the browser and have them work on older browsers. Anyone who only supports modern browsers could opt for a package without the transpiler bundled, and nunjucks-slim would be unchanged, since it doesn't compile anything.

Since I'm convinced of the feasibility, I'm working on making pregenerator even smaller by replacing the pseudo-fork of babel-types with ast-types, and rewriting it in typescript.

As far as async macros, filters, loops. and the rest go: I don't think the current approach makes much sense, where we have a handful of async versions of tags. I'm partial to jinja2's approach, where the environment can be in sync or async mode. In async mode, everything is asynchronous; even otherwise synchronous filters would be wrapped in a function to make them async. Having Environment.render and Environment.renderSync seems the most idiomatic to me, for node.

@groenroos
Copy link

@fdintino Any update on Nunjucks 4? Is there something that I, and/or the wider community, could help you with? :)

@fdintino
Copy link
Collaborator Author

fdintino commented Aug 1, 2024

I've just made public the nunjucks v4 repository: https://github.com/nunjucks/nunjucks4. It supports all of the features listed in this issue description, and is correctly characterized by my comments on this issue, but it is not yet complete. There is a tracking ticket where I've listed the work that needs to be done.

I spent a fair amount of time trying to get this project to an alpha state. I welcome any and all assistance with completing the remaining work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants