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

2.0 Planning #294

Closed
4 of 9 tasks
rexm opened this issue Mar 30, 2019 · 40 comments
Closed
4 of 9 tasks

2.0 Planning #294

rexm opened this issue Mar 30, 2019 · 40 comments

Comments

@rexm
Copy link
Member

rexm commented Mar 30, 2019

As a celebration of Handlebars.Net's millionth installation, it seems like a good time for:

Handlebars.Net Version 2.0 🎉

This thread will be to discuss what goes into 2.0. Really appreciate everyone's involvement, even if it's just bringing your experience and input to the table.

Call for Input

So far, here's the changes being considered (in no particular order): As consensus develops in this thread, approved items will get a [x] to indicate they're officially part of the 2.0 roadmap. Please review, comment, and add your own:

Special thanks

to @mcintyre321, @Magentaize, @dejx, @amaclean, @vzwick, @axelheer, @sandorfr, @leniency, @pmccloghrylaing, @mikeprince3, @StefH, @huysentruitw, @abraham-fox, @ondrejtomcik, @mohd-akram, @EBMike, @JvanderStad, @katavasha, @Andrew-Hanlon, @nblumhardt, @crra, @esskar, and the many others who've helped make this library useful to people all over the world in the last 5 years!

@rexm
Copy link
Member Author

rexm commented Apr 27, 2019

Plan to address #292

@rexm
Copy link
Member Author

rexm commented Apr 27, 2019

Plan to address #297

@rexm
Copy link
Member Author

rexm commented May 26, 2019

Plan to address #301

@johnknoop
Copy link

Suggestion for v2:

I'm not sure if this is even possible using the tools available, but here's my idea:

We only use Handlebars to render html e-mails, and I imagine that's what most people use this library for, since rendering website UI's using Handlebars in .NET seems like an unusual thing to do. And given the nature of e-mail clients, a common setup is to produce the e-mail body in this sequence:

  1. Check if the template is already compiled. If not, compile it.
  2. Render the template.
  3. Run Premailer.Net or any other CSS inliner.
  4. Extract image paths from img elements in the html, and create CID-embedded attachments.

Now, step 3 and 4 doesn't really have any dependency on the context data used in step 2. Therefore, it's a shame we can't do step 3 and 4 as part of the compilation, so it only has do be done once per template. Now I have no idea how the execution flow looks when it comes compiling a Handlebars template, but it would be nice if one were able to register hooks that get called by Handlebars.Compile:

Handlebars.RegisterCompileHook(CompileHooks.AfterAllPartialsHaveBeenIncluded, (string html, Dictionary<string, object> templateProperties) =>
{
    templateProperties["EmbeddedImages"] = ExtractImagePaths(html, "/path/of/templates");
    return ApplyCssInlining(html);
});

And the return type of Handlebars.Compile would be:

interface CompiledTemplate
{
    string Render(object context);

    // Any properties that have been set by hooks
    Dictionary<string, object> templateProperties)
}

With this approach, the task of rendering an e-mail body would be so much quicker since all that has to be done is step 2 instead of steps 2, 3 and 4.

I don't know enough about the internals of how Handlebars templates get compiled. But if this seems reasonable and possible to do, then I'd be happy to help out implement it. But I don't want to add it to the checklist above before I know if this is even a sensible thing to do and if it's inline with the vision of this project.

@rexm
Copy link
Member Author

rexm commented Jun 22, 2019

The main goal of this project is to have a .NET native implementation of the handlebars JS library. The handful of times we’ve bent and added features that aren’t in the original handlebars library, it’s ended up being a mistake. That being said, since .NET and JS are different platforms, it can make sense to diverge. It should make sense based on those differences and be universally useful - contrary to your perception, while generating emails is one popular use-case, it is also widely used as a website templating engine, a code generator (not something I’d recommend, but it gets used that way) and a lot of others. Compiler hooks could make sense, but we’d need to work on a model that is useful to everyone and doesn’t commit us to supporting contracts we end up regretting.

@johnknoop
Copy link

johnknoop commented Jun 22, 2019

Maybe compiler hooks could be part of the extensibility model you mention above. But I don't even know if html in -> html out would make sense as a contract for such hooks. You might work with other primitives than strings of html during the assembly process?

Also, everything I've written is based on the assumption that partials are sucked in during compilation, and not later on when the compiled template is rendered. Is this a correct assumption btw?

@rexm
Copy link
Member Author

rexm commented Jun 22, 2019

When a template is compiled and a partial is referenced, that reference is replaced by a function to look up the partial by name and invoke it during execution. What are you thinking you could do with that?

@johnknoop
Copy link

In the e-mail scenario, you typically wanna apply the CSS inlining after all partials have been pulled together into a single html document, because you might have styles coming from one partial and the elements from another. I just assumed this was how the compilation process looked, meaning the result of the compilation was a complete hbs document where all partials have been sucked, and the only thing happening during rendering was turning the remaining handlebars expressions into strings using the context data.

So given this, perhaps there's no real use for compilation hooks in my case.

@rexm
Copy link
Member Author

rexm commented Jun 23, 2019

Have you considered inlining before template compilation? Even as part of the build...

@johnknoop
Copy link

I have, The problem is that styles might reside in a partial for reusability, so it's only when a template has been assembled into a complete html file that I can safely apply the inlining.

@rexm
Copy link
Member Author

rexm commented Jun 23, 2019

I suggest exploring some other structures to achieve the same result without introducing the same challenges.

@304NotModified
Copy link
Contributor

I like the post compile transformations. Even .net has nowadays a compiler sdk.

@leosdad
Copy link

leosdad commented Sep 20, 2019

Retaining unsolved expressions for later evaluation would be really useful. I hope you consider implementing this as an option.

@leosdad
Copy link

leosdad commented Sep 22, 2019

Also, code documentation.

@gab
Copy link

gab commented Oct 1, 2019

Suggestion for 2.0: Been a while since I used HandleBars, but I remembered really wanting to be able to turn off HTML escaping of some characters, and it wasn't possible - I had to run HtmlDecode() the output, which in any performance-sensitive scenario isn't great. Would be great if it could be added, if it hasn't already been since then.

@rexm
Copy link
Member Author

rexm commented Oct 2, 2019

@gab added!

@gab
Copy link

gab commented Oct 2, 2019

Wow, that was faster than fast! Fantastic, thank you.

@rexm
Copy link
Member Author

rexm commented Dec 18, 2019

@kitsu it is possible but a little awkward - when used in a subexpression, the contents of the textwriter in that scope are captured and passed back as a string.

@kennethchoe
Copy link

@kitsu, @rexm - not sure if I understood correctly, since we did make eq helper. It was somewhat confusing in the beginning since everything is recognized as string except that the very initial pass-in is recognized with its data type as-is. We looked at HandlebarUtils.IsFalsy() and found that empty string can be used as false condition return value. With that contract, we made eq, and and or.

	Handlebars.RegisterHelper("eq", (writer, context, parameters) =>
	{
		var val1 = parameters[0].ToString();
		var val2 = parameters[1].ToString();
		var isEqual = "";

		if (val1 == val2)
		{
			isEqual = "1";
		}

		writer.Write(isEqual);
	});

@rjendoubi
Copy link

Retaining unsolved expressions for later evaluation would be really useful. I hope you consider implementing this as an option.

I'd like to second this and present another use-case.

We have an interface which allows our users to create bespoke Reports, each of which can contain an arbitrary number of queries to our database, and output the results of those queries to a number of templates of different formats: CSV, Excel, and an HTML format facilitated by Handlebars.NET.

Because we don't know how many searches our users will include, we can't presume to hold all the results in memory and insert into the output template in one shot. What we'd prefer to do, which we do for the other output formats, is run the search, insert the needed values into the template (which is stored on disk), and then throw away the results and move on to the next one.

I appreciate the challenge has already been issued on the SO question to send a PR 🙂 If it's agreed this would be a desirable feature (that on the face of it wouldn't necessarily kill performance), can it be added to the roadmap?

@akamyshanov
Copy link

Even though not part of Handlebars spec, formatting support as described in #274 would be very handy.

@StefH
Copy link
Collaborator

StefH commented Apr 12, 2020

Adding an extra helper library which contains some generic functions like which are supported for javascript; https://github.com/helpers/handlebars-helpers

See also Issue #343
And my work-in-progress example code : https://github.com/helpers/handlebars-helpers

(Note this can also be build for the current version.)

@sferencik
Copy link

sferencik commented May 29, 2020

Helpers should return values rather than write to the textWriter. Although sub-expressions have a way of dealing with this (capturing the text output), it is somewhat awkward.

Also, the "helpers write" approach is inconsistent with Handlebarsjs, where helpers return the result.

Also, the "helpers write" approach causes an asymmetry between HelperExpressions and PathExpressions. I'm now trying to implement the "helper missing" functionality (#350), which should kick in if the template source contains something like {{foo}} or {{foo bar}} and foo is unbound (and absent in the data). But notice that (with foo unbound) {{foo}} compiles into a path expression (which is supposed to return the result) while {{foo bar}} compiles into a helper expression (which is supposed to late-bind and write the result to the textWriter). As a result, if the helper-missing hooks write (as opposed to return) we'll need to perform similar manoeuvers like we do for sub-expressions.

(Am I missing something? I'm very new to Handlebars.Net. Is there a reason the helpers must write?)

@bags1976
Copy link

The handlebars api has a method to parse the ast/tokens returned prior to invoking the compile.

https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md

This would be very useful to allow a user to extract the tokens from a template and then be able to extract the variables required for a particular template. The feature of extracting the variables was requested long ago here in the native handlebars.js and ultimately is what I would love to see implemented as well.

handlebars-lang/handlebars.js#1207

Currently there is no way to have the compiler parse the template for tokens and simply return the results in the interface and they are internal classes / methods so there is no good way to extract this information now.

@elkhawajah
Copy link

The v1.0 is not compatible with .net core 2.2 right ? what is the alternatives for a compatible version for a production app ? Seems v2.0 not officially released yet.

Thoughts please.

@StefH
Copy link
Collaborator

StefH commented Sep 9, 2020

The current Handlebars.net does support NETStandard 2.0 so it can be used by .net core 2.2

@elkhawajah
Copy link

elkhawajah commented Sep 9, 2020 via email

@oformaniuk
Copy link
Member

oformaniuk commented Oct 16, 2020

Suggestion for v2:

I'm not sure if this is even possible using the tools available, but here's my idea:

We only use Handlebars to render html e-mails, and I imagine that's what most people use this library for, since rendering website UI's using Handlebars in .NET seems like an unusual thing to do. And given the nature of e-mail clients, a common setup is to produce the e-mail body in this sequence:

  1. Check if the template is already compiled. If not, compile it.
  2. Render the template.
  3. Run Premailer.Net or any other CSS inliner.
  4. Extract image paths from img elements in the html, and create CID-embedded attachments.

Now, step 3 and 4 doesn't really have any dependency on the context data used in step 2. Therefore, it's a shame we can't do step 3 and 4 as part of the compilation, so it only has do be done once per template. Now I have no idea how the execution flow looks when it comes compiling a Handlebars template, but it would be nice if one were able to register hooks that get called by Handlebars.Compile:

Handlebars.RegisterCompileHook(CompileHooks.AfterAllPartialsHaveBeenIncluded, (string html, Dictionary<string, object> templateProperties) =>
{
    templateProperties["EmbeddedImages"] = ExtractImagePaths(html, "/path/of/templates");
    return ApplyCssInlining(html);
});

And the return type of Handlebars.Compile would be:

interface CompiledTemplate
{
    string Render(object context);

    // Any properties that have been set by hooks
    Dictionary<string, object> templateProperties)
}

With this approach, the task of rendering an e-mail body would be so much quicker since all that has to be done is step 2 instead of steps 2, 3 and 4.

I don't know enough about the internals of how Handlebars templates get compiled. But if this seems reasonable and possible to do, then I'd be happy to help out implement it. But I don't want to add it to the checklist above before I know if this is even a sensible thing to do and if it's inline with the vision of this project.

@johnknoop, starting from 2.0 this scenario would be achievable using low-level IExpressionMiddleware interface. Interface allows transformations on Expression Tree before it is compiled. In order to get desired behaviour you'd need to implement custom ExpressionVisitor to walk the tree and perform steps 3 and 4.

@StefH
Copy link
Collaborator

StefH commented Oct 16, 2020

Maybe already in place : make sure the library is signed.

@StefH
Copy link
Collaborator

StefH commented Nov 4, 2020

@zjklee
Maybe add a config option to "keep the replacement as-is if the value cannot be replaced"

Like:

string source = @"My name is {{Name}} and I work at {{Job}}";
var template = Handlebars.Compile(source);
var data = new {
    Job = "Walmart"
};
var result = template(data);

Result should be:

My name is {{Name}} and I work at Walmart

https://stackoverflow.com/questions/64379082/handlebars-net-how-to-change-behavior-if-data-does-not-exist

@oformaniuk
Copy link
Member

@StefH , see /issues/373

@StefH
Copy link
Collaborator

StefH commented Nov 4, 2020

@zjklee This looks perfect!
Maybe you can answer that StackOverflow question? The credit is yours ;-)

@sferencik
Copy link

@zjklee what's your ETA on this? We're keen to start using some of the new features; should we wait or build our own pre-release version?

@oformaniuk
Copy link
Member

oformaniuk commented Dec 2, 2020

@sferencik originally I was planning to address #97 before publishing a release version as it would introduce a dramatic breaking change to the public API. However it looks like I'd defer it to 3.0 release.
Regarding ETA: I'm planning to release one more pre-release version today/tomorrow after #391 is merged. This pre-release package would be the last one before I publish release version unless any critical bugs are found. I guess you can expect a release version by the end of the week.

@sferencik
Copy link

That would be great, thanks! Is there something I can help with?

@oformaniuk
Copy link
Member

That would be great, thanks! Is there something I can help with?

I'm trying to keep the list of issues up to date so in case you want to contribute I'd definitely be happy to accept PRs.

@oformaniuk
Copy link
Member

Release 2.0.0 is out.

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

No branches or pull requests