I've been messing around with a backbone.js app using require.js and a handlebars templates (I've added the AMD module stuff to handlebars) and just read that pre-compiling the templates can speed it up a fair bit.
I was wondering how I would go about including the precompiled templates with requirejs. I have a fair few templates to compile (upwards of 15), so i'm not sure if they should all be in the same output file or have their own once compiled. Also, from what it seems, the compiled templates share the same Handlebars namespace that the renderer script uses, so I'm not sure how I would go about that when requiring the templates in my files.
Any advice would be awesome!
A simple approach is to create a RequireJS plugin based on the existing text! plugin. This will load and compile the template. RequireJs will cache and reuse the compiled template.
the plugin code:
// hbtemplate.js plugin for requirejs / text.js
// it loads and compiles Handlebars templates
define(['handlebars'],
function (Handlebars) {
var loadResource = function (resourceName, parentRequire, callback, config) {
parentRequire([("text!" + resourceName)],
function (templateContent) {
var template = Handlebars.compile(templateContent);
callback(template);
}
);
};
return {
load: loadResource
};
});
configuration in main.js:
require.config({
paths: {
handlebars: 'libs/handlebars/handlebars',
hb: 'libs/require/hbtemplate',
}
});
usage in a backbone.marionette view:
define(['backbone', 'marionette',
'hb!templates/bronnen/bronnen.filter.html',
'hb!templates/bronnen/bronnen.layout.html'],
function (Backbone, Marionette, FilterTemplate, LayoutTemplate) {
...
In case you use the great Backbone.Marionette framework you can
override the default renderer so that it will bypass the builtin
template loader (for loading/compiling/caching):
Marionette.Renderer = {
render: function (template, data) {
return template(data);
}
};
Have a look at the Requirejs-Handlebarsjs plugin: https://github.com/SlexAxton/require-handlebars-plugin
Related
I am creating a NodeJS app which uses AngularJS for it's front-end. I am Also using RequireJS to load in the JS dependencies and then instantiate the Angular app. Here is what I am trying to do:
Within my HTML file (written in Jade) I include the RequireJS files and then call the RequireJS config using the 'data-main' attribute:
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
script(type="text/javascript" src="/bower_components/requirejs/require.js" data-main="/main.js")
My main.js file looks as follows:
"use strict";
function(require) {
require(['/assets/requiredPathsAndShim.js'], function(requiredPathsAndShim) {
require.config({
maps : {
// Maps
},
paths : requiredPathsAndShim.paths,
shim : {
// Modules and their dependent modules
}
});
angular.bootstrap(document, ['appNameInHere']);
});
})(require);
I have an external file which contains an object with my routes '/assets/requiredPathsAndShim.js' and it looks like follows:
"use strict";
(function(define) {
define([], function() {
return {
paths : {
'angular' : '/bower_components/angular/angular'
}
};
});
})(define);
I will add that my NodeJS/Express app has the 'bower_components' folder set to serve static files and this is working fine.
Whenever I try and instantiate the AngularJS app using the 'angular.bootstrap...' method it tells me Angular is not defined. I can't see why this is happening and haven't been able to figure it out yet. O can't see any problem with my routes to the Angular files. Can anyone see or suggest why this may be happening?
Thanks!
Just managed to crack it! I had to place the 'angular.bootstrap' call in a callback function of the require.config method as the app was trying to call AngularJS before it had been defined.
Hope this helps anyone in the future.
I am building a large scale application using backbone and marionette. Instead of using underscore templating engine, I am planning to use dust.js.
I have found marionette-dust plugin which could do the job but currently I am at a loss in understanding how to use it with require.js. Also, it there a better way of implementing dust besides using this plugin?
Feedback appreciated.
Following is the code in sample application
testView.js
define(["app", "templates/test.dust"], function(app, testTpl){
app.module("test.view", function(view, app, Backbone, Marionette, $, _){
view.list = Marionette.ItemView.extend({
template: testTpl,
});
});
return app.test.view;
});
test.dust
(function() {
dust.register("demo", body_0);
function body_0(chk, ctx) {
return chk.write("This is Dust.js Test");
}
return body_0;
})();
main.js
requirejs.config({
baseUrl: "assets/js",
paths: {
backbone: "vendor/backbone-min",
jquery: "vendor/jquery-min",
marionette: "vendor/backbone.marionette-min",
tpl: "vendor/tpl",
underscore: "vendor/underscore-min",
dust: "vendor/dust",
dustHelpers: 'vendor/dust-helpers',
dustMarionette: "vendor/backbone.marionette.dust",
templates: 'templates/compiled',
},
shim: {
jquery: {
exports: 'jquery'
},
underscore: {
exports: "_"
},
backbone: {
deps: ["jquery", "underscore"],
exports: "Backbone"
},
marionette: {
deps: ["backbone"],
exports: "Marionette"
},
dust: {
exports: 'dust'
},
dustHelpers: ['dust'],
templates: ['dust', 'dustMarionette']
}
});
require(["app"], function(app){
app.start();
});
I'm the author of that plugin. Basically you need to define the three dependencies
'backbone', 'dust' and 'marionette' in your Requirejs config file and then define
the AMD version of this module as a dependency after marionette during the initial application setup.
The plugin is written under the assumption that you have compiled all of your dust templates and they are in the dust cache (you should find details on how to do that in the dust documentation). You may see why if you have a look at the plugin source code. Inside of each your views simply set the template property to the name of the template in the dust cache that you want to use.
The plugin overrides the render function in the Marionette.Renderer object that is used by all views. So basically under the hood, Marionette is calling the render function of this plugin which renders the templates with Dust and then returns the HTML. Marionette's documentation here mentions this is the best way to provide custom rendering.
I've tried to outline all of this in the Readme file for the plugin but if you think it can be improved (which I don't doubt it can) then please let me know which areas are unclear.
I've written a Yeoman generator called generator-maryo to provide scaffolding for a marionette and dust web application. There are still a few todos in there but it provides you with a good foundation on how to use the marionette-dust plugin with marionette. If I can be more explicit in any area then let me know.
EDIT AFTER CODE ADDED TO QUESTION
The marionette-dust plugin is access by template name, not by the compiled template function. So essentially you need to run the compiled template function (which it should do by itself as it's an anonymous function), then it will be placed in the dust cache under the name "demo". So your item view should look like:
define(["app", "templates/test.dust"], function(app, testTpl){
app.module("test.view", function(view, app, Backbone, Marionette, $, _){
view.list = Marionette.ItemView.extend({
template: "demo",
});
});
return app.test.view;
});
Note that all I did was set the template property to "demo". Also, is this all of the code for the demo app? You need to actually show the view using a region or you can just call the render function on the view manually. To get something working quickly, you can do something like this:
myView = new app.test.view;
$('body').append(myView.render().$el);
Also, why are you wrapping the view in a module block? As you're already using RequireJS, Marionette's module system is not really necessary. I am in the process of writing a demo app for Marionette using generator-maryo which will probably explain a lot. Keep an eye out for it in the github repo.
I am using an .ejs template in my view. But for some reason the view does not load the given template. It returns undefined. Here's the code:
sandplate2.applicationView = Backbone.View.extend({
el: 'div',
template: _.template($("appl.ejs").html()),
initialize: function(){
console.log("Application view initialize");
_.bindAll(this, "render");
this.render();
},
render: function(){
console.log("Application view rendering");
this.$el.html(this.template());
return this;
}
});
Do I have to configure something else in order to load a template?
I structured my app using Yeoman. I used the init and backbone generators.
FYI - The template I am trying to load is loaded in the index.html using a script element.
If you built it using Yeoman, take a look at app.js to see if you are using Backbone.LayoutManager. You might have to change the configuration there for EJS to work. By default, I think it should be expecting Underscore templates.
I'm using Handlebars and I updated my app.js to look like this:
Backbone.LayoutManager.configure({
manage: true,
paths: {
layout: "templates/layouts/",
template: "templates/"
},
fetch: function(path) {
path = path + ".html";
if (!JST[path]) {
$.ajax({ url: app.root + path, async: false }).then(function(contents) {
JST[path] = Handlebars.compile(contents);
});
}
return JST[path];
}
});
I also added Handlebars to the module's define() call, passing in 'Handlebars' as a reference. You might need to do something similar for EJS.
Please try with latest backbone genearator with yeoman 1.0beta.
We have made lot of improvements on it including Precompiling ejs templates. You don't want to worry about templates, yeoman will precompile and load it for you.
A sample generated code for input-view is provided below.
Todo.Views.InputView = Backbone.View.extend({
template: JST['app/scripts/templates/input.ejs'],
render: function(){
$(this.el).html(this.template());
}
});
Conventions aside, it looks like the problem is simply your jQuery lookup.
_.template($("appl.ejs")...
$('appl.ejs') isn't a valid DOM selector unless you have an element like this in your index.html
<appl class="ejs"></appl>
If you're trying to target your template with jQuery, give it an ID or something that jQuery can find like so:
<script type="text/template" id="appl">
<div></div><!-- template html here -->
</script>
// later...
$('#appl').html()
// will get your template html
However, as others have mentioned, in a yeoman and require.js workflow, you can ask require.js to fetch the template for you as text and throw it around as a variable before creating an underscore template.
In Addy Osmani's ToDo MVC example for require.js + Backbone: https://github.com/addyosmani/todomvc/blob/gh-pages/dependency-examples/backbone_require/js/main.js, he's using
Backbone.history.start() // line #31
without actually requiring Backbone. How/why does this work? Is the shim enabling this? Or am I missing something obvious?
If you have a look in the code, view/app.js is actually requiring Backbone.
And the backbone shim is exporting the global Backbone variable.
If no other modules will actually require the shim, it won't be loaded, so it won't be accessible.
You can try to remove the 'views/app' requirements in main.js to see for yourself.
As #ChristiMihai mentioned, Backbone created a global Backbone object, correct. Let me give you an example of what I do in my Require.js / Backbone / Handlebars app:
First, I include Require config in <head>:
var require_config = {
baseUrl: "/javascripts",
waitSeconds: 5,
paths: {
'cdnjs': 'http://ajax.cdnjs.com/ajax/libs',
'aspnetcdn': 'http://ajax.aspnetcdn.com/ajax',
'cloudflare': 'http://cdnjs.cloudflare.com/ajax/libs',
'local': '/javascripts'
}
}
if (typeof require !== 'undefined') {
require.config(require_config);
} else {
var require = require_config;
}
After that I bootstrap a require module, e.g:
define([
'app'
],
function() {
console.log('Homepage module');
/*
... this is the meat of your app...
you can add other dependencies beside `app` too
*/
});
Now app is the main dependency which resolves via baseUrl to /javascripts/app.js and includes all necessary deps in order, and looks like this:
define([
'order!cdnjs/json2/20110223/json2',
'order!cloudflare/underscore.js/1.3.1/underscore-min',
'order!cloudflare/backbone.js/0.9.2/backbone-min',
'order!handlebars/handlebars-1.0.0.beta.6.min',
'order!lib/ns',
'bootstrap'
], function(){});
I have a Backbone view as a requirejs module. The problem is that requirejs load the http://localhost/remote/script/here.js before the view is even initialized. Is it because the script isn't inside a requirejs module?
define([
'jquery',
'undescore',
'backbone',
'http://localhost/remote/script/here'
], function($, _, Backbone, Luajs){
var View = Backbone.View.extend({
initialize : function(options) {
},
render : function() {
this.$el.html('<p>my view</p>')
return this;
}
});
return View;
});
The array you have as the first argument to define is the depedencies of your view. So yes it is loaded and parsed before the View.
Also note that unless you use modified versions of backbone and underscore, they ar not AMD compliant. You will need to wrap them with a plugin to load them properly.
you try to define the view Backbone after the load the module.
You can do this, in the define () method of RequireJS. The array of this function contains parameters that defines module dependencies.