-
Notifications
You must be signed in to change notification settings - Fork 7
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
Recursive resources #103
Comments
I looked at handlebars and express3-handlebars, and there's no way to intercept partials rendering. Instead of parsing hbs files ourselves, we should use handlebars block helpers, they get called automatically by handlebars, for templates and partials. The comments block at the top of each partial would still be ignored by Solidus. A Simple examplepage1.hbs:
page2.hbs:
/page1.json:
With nested resourcespage1.hbs:
/page1.json:
With a preprocessorThis new method doesn't work well with preprocessors. Nesting 3 or 4 resources, then a preprocessor helper, just looks weird. Instead, the helper could accept a preprocessor argument. That preprocessor would be responsible to load its required resources, and would only return the resulting generated context. page1.hbs:
complex.js
/page1.json:
|
Issue with this change: we'll need to fetch the resources and run the preprocessors in sync, since handlebars is not async. asyncblock is a potential solution. |
Turns out I'm unable to make an async call sync. All the async libraries use fibers, which is fine, but as soon as an async call is made in the request, the current fiber is lost. The solution would be to use one of these libraries all over the place, and never make async calls without them. And I'm not sure it's even possible, since express3-handlebars makes async calls itself, to load the templates and partials from the file system. I also tried handlebars-async, but I couldn't make it work, maybe it's got something to do with express3-handlebars. The Handlebars dudes think making async helpers is a bad idea anyway, so let's find a better solution. 😠 |
Let's explore what a controller might look like. Organizing clientside code around routes is actually something else I've wanted to accomplish. Currently that's done with hackish stuff like: if( $('body').attr('id') === 'page-index' ){
...
} I've much preferred implementing a router like Crossroads.js and simply matching up code to call with the view routes derived from the filesystem. One thing I really like about this is that the parameters in the URL are all right there for reference. I'd really like to share the same method for defining routes between the server and client and have some ability to share code as makes sense between those layers. Browserify definitely simplifies that. The question really is how to handle routing and resource fetching though. There's two projects that come to mind that have explored this with the same goal of server/client parity:
Backbone has been making inroads for years though in the web app world and somewhat recently in the WordPress world lately as well for more app-like presentation of content. But while it adds structure it's still very flexible and unopinionated which is great but of course translates to difficulty for newcomers. Will need some time to compose my thoughts on why our case is different or at least what layers perhaps we need to layer on top, a la Marionette.js. Two more conventional MVC projects of note we should also look at: One fundamental difference perhaps between us and all these projects is that we're |
We should also look at routers specifically:
One (somewhat) trivial thing I like about Crossroads.js is that it uses our Check it out in use for Storyteller.io: https://github.com/SparkartGroupInc/hipster-tools/blob/master/app/client/app/routes.js#L15. I am thinking of how our portable blocks might extend such a routing table object with a reserved base route. |
I found express-hbs, which supports async handlebars helpers. I haven't tried to replace express3-handlebars (express-hbs has new helpers names), but I was hoping to find a solution in their codebase. Turns out each async helper returns a unique ID in sync, and they do a bunch of replace with the actual values from the helpers once the template is rendered and the async helpers are done. I gueeesss we could do something similar, but I'm not sure what would happen if for example a helper was to modify the global context... |
I finally managed to get the
Advantage: templates and partials can be easily reused, provided that the auth file is present between projects. Problems: the resources are not added to the global context, since they're computer at runtime and only available during the helper's block. This means they're not included in the |
Another disadvantage: if multiple resources are used in a page, they will be retrieved in series, as they appear in the template. We currently request them all in parallel, so this new method is slower. And if a resource timeouts, the remaining resources will never be requested (if we maintain that 5s max render time). |
Yeah the solution for debugging that I thought would actually work better is extending a global debugging object in the browser with the responses in each block helper. That way you could easily explore the context in your developer console. But that doesn't solve clientside resource access and 5 seconds is definitely optimistic for a % of time, especially for pages with more resources. |
An ugly solution would be to automatically output a script tag with the resource content whenever we get them, the end result would technically be the same 😕 Also, the 5 seconds delay is arbitrary, we can increase it, but the problem is still the same: we lose some response time because we don't request all resources in parallel anymore. |
Right that's what I'm saying. |
I was talking about clientside resource access. Until we have a better client library. |
BTW, is there a reason we're not using url-template instead of manually building the resource urls? We already use addressable in Hipster. They both implement RFC 6570. Proposed setup. The idea is to have the resources and preprocessors for each template (page or partial) in modules equally accessible from the server and the client (through Browserify). Here's an example: Serverviews/index.hbs
views/tweets.hbs
preprocessors/index.js
preprocessors/tweets.js
We need to trigger the right preprocessors when a page is requested. Solidus can do it magically by looking at matching file names, or we can ask the dev to explicitely wire the preprocessors with the templates. Given that we don't want magic, I see 3 solutions:
ClientWe need to make precompiled templates available to the client, see express3-handlebars example. To share the preprocessors with the client, use Browserify. The modules usage will be similar to the server side, depending on which solution above we use. routes.js
|
We can actually use this for template pre-compilation: https://github.com/epeli/node-hbsfy Here's my take on routes.js we were looking at in our discussion: module.exports = {
'/index.hbs': {
bonjovi_news: get('news.js', { 'items': 5, 'filter[tag]': 'featured' }),
bonjovi_photos: get('photos.js', { 'items': 5, 'filter[tag]': 'featured' })
},
'/news.hbs': {
bonjovi_news: get('news.js', { 'items': 15 })
},
'/news/{id}.hbs': {
bonjovi_news: get('news.js')
},
'/blocks/tweets.hbs': {
tweets_data: get('blocks/news.js')
}
} The key takeaway here is just that the params object can be extended from whatever is set at the preprocessor level through the optional arguments shown here as well as the path parameters that are defined in the filesystem. |
Storyteller Editor allowed resources (and preprocessor functions) to be defined in partials, which enabled self-contained blocks that were portable, easily inserted into any page. IIRC, resources were fetched, preprocesser functions were run, and a template was rendered with the resulting context within each partial before it was inserted into the parent page. For any partials that relied on a preprocessor this could be quite slow, especially considering that a fresh V8 environment was instantiated for each preprocessor via therubyracer.
While Solidus views can be used both as pages and partials, any page configuration is ignored when views are inserted into pages as partials. If a partial has any dependencies on particular resources and anything else the context must be passed to the partial as an argument, scoped where appropriate.
For example, given this context:
A partial that renders photos from a Facebook album would need to be inserted like this:
There’s definitely some merit to this approach, it’s evident what context partials have, and resources can be configured for the page. The downside is that if looking at the partial in isolation any API resource dependencies aren’t clear unless perhaps they are configured there for use as a page or comments are left. The resource must be setup manually regardless and can’t be updated centrally.
Reusability
The more significant downside is that this limitation doesn’t allow for the self-contained blocks we had in Storyteller. This isn’t so much of an issue within a single site, but we miss out on an opportunity with significant potential for enabling rapid development: reusing blocks across sites. Coupled with the versioning and distribution that npm/Git provide, we could reuse such blocks in the same way we do JS and CSS libraries.
Such blocks are essentially a more performant, highly customizable alternative to using iframes for such reuse. HTTP requests are kept to a minimum since the blocks depend upon assets in the parent site’s asset bundles. This also means the site’s assets can adjust CSS and interact with JS APIs as needed — iframes in contrast require serverside customization controllable via URL parameters.
Eventually Web Components will provide a better alternative altogether, but that’s still distant and doesn’t address API dependencies other than accessing them clientside. We should keep where those efforts are going in mind for our own solution however. CSS-Tricks has a good rundown of the details.
Recursive Resources
Here’s what a middleground between Storyteller Editor and Solidus might look like:
The text was updated successfully, but these errors were encountered: