ericf / express-handlebars

A Handlebars view engine for Express which doesn't suck.
BSD 3-Clause "New" or "Revised" License
2.31k stars 384 forks source link

Intercepting Template Before Compile #39

Closed KidA78 closed 10 years ago

KidA78 commented 11 years ago

Eric, big fan of this module. Powerful stuff.

This is a question, not an issue: I would like to intercept my templates and partials before they get compiled and cached, and process them with another module.

For example, just say I wanted to use juice to inline all my css, is there a mechanism for applying such transformations? If it were in the module it would probably happen somewhere in express-handlebars.js line 132.

What would you recommend, given the underlying structure, if there was a way to preload the files, transform them. Is it possible would you say, without extending the module itself?

Our other option is to just juice the templates in a grunt script, but it would be good to have this in memory since we don't have a lot of templates....

ericf commented 11 years ago

Could you just use a Handlebars block helper for this? I've done similar stuff like this before:

KidA78 commented 11 years ago

Thanks for getting back to this, Eric! So, that's an interest approach you suggest. I ran some tests, but my hunch what may have happened is right.

you see juice takes an HTML string + Css file, and inlines all the stylesheets in the HTML elements. Putting this functionality within a block helper, the juice still happens with every request to render the template, even if the template has already been cached. It seems that the Helper functions are applied at runtime.

I guess what I am thinking of, is:

File.hbs (from file system) ---> Compiled JS in ExpHbs Cache To somehow intercept the HBS from all the templates along the way to the cache, and get them juiced, so they have the inline CSS already in them within the cache.

ericf commented 11 years ago

This sounds like something you'd want to do at the Express View level by overriding the way Express calls into the rendering process so you can pass along your cached HTML or post process the HTML before sending it as the response. Handlebars compiles strings into functions, and Express-Handlebars caches those compiled functions, not HTML strings.

The reason this packages also has a cache for files is to support both compiling and precompiling templates while only needing to go to disk once. This is an implementation detail where I made a trade off of consuming more RAM instead of going to disk more than once per template when the cache option is set.

KidA78 commented 11 years ago

I think in our case especially, consuming more RAM vs going to disk, is fine. We have a few nested templates, that are repeatedly called for different items rendered (can be hundreds in some cases), going to memory for a small template beats 100s of reads, per event. That said, I would like to process the template before it gets compiled. En route to compilation. Currently we are post processing the HTML on its way out from express, which is resource intensive.

What i'm probably gonna have to end up doing, is writing a custom grunt task, whereby I can create a mirror directory of processed .hbs files, that way they're already in the file system, created at build time, after deploy. The goal really is to only process each template once before compilation. So if it's not possible to do it in between read from disk and compile, then it will have to be at build time... I was trying to see if there was a hook, essentially at line 134 of expess-handlebars. Digging through the handlebars API itself, doesn't seem to be a way to do it there either.

Could you think of any other needs for some sort of pre-process function that sits in between read and compile? I guess most folks resort to build tasks for this sort of thing.

ericf commented 11 years ago

Could you think of any other needs for some sort of pre-process function that sits in between read and compile? I guess most folks resort to build tasks for this sort of thing.

I think your use-case is valid without needing others to solidify it. I would be willing to add a hook by simply adding a compileTemplate() method on the prototype which you'd be able to easily override. The other option would be to subclass Express' View class and override the rendering process to add another level of caching at the Express View level.

Note: There's a app.set('view' CustomViewClass) option that we got implemented. You can see an example of this approach done here: https://github.com/yahoo/express-view

ericf commented 11 years ago

@KidA78 I will replace this chunk of code with something that first loads the template, then delegates to a public compileTemplate() method passing it the args which loadTemplate() was called with. And I'll make it all async.

This will allow you to hook in and override compileTemplate(), and wrap the call the original method. This will give you the chance to do whatever you want before/after the Handlebars compiler runs and before the template is cached. Sound good?

ericf commented 11 years ago

@KidA78 does #41 work for you?

KidA78 commented 11 years ago

Eric, I just took a look at the changeset and it looks good. Could you provide as well an update to your examples section? Will clarify how this could be used.

On a side note, I'm working on creating adapters for some of these html processors, for handlebars templates. Will be a great coupling with this feature you just put in!

ericf commented 11 years ago

Yeah I'll look into getting the docs updated. I want to get to a v1.0 on this package, and the Issues Milestones capture what's left.

Usage should be straight-forward:

var hbs             = require('express3-handlebars').create(),
    compileTemplate = hbs.compileTemplate.bind(hbs);

hbs.compileTemplate = function (template, options, callback) {
    // Pre-process template here.

    compileTemplate(template, options, function (err, compiled) {
        // Post-process template here.

        callback(err, compiled);
    });
};
KidA78 commented 11 years ago

Perfect, thanks!

ericf commented 11 years ago

Updated code sample to bind correct context.

ericf commented 10 years ago

This feature is now implemented in #68