Histone - cross-platform template engine
Node.js® installation
npm install histone
In case if you do it for production, don't forget to tune it (see description below):
npm install histonecd node_modules/histone/buildnpm installnode build.js --format=commonjs --exclude-parser
It works absolutely the same way even if you specify Histone as a project dependency in your package.json file:
And then the production build would look like:
npm installcd node_modules/histone/buildnpm installnode build.js --format=commonjs --exclude-parser
Running tests
In case if you wish to build some feature or just play around with it, you can run tests in order to see if your changes breaks something important:
cd node_modules/histone/testnpm installnode test.js
By default tests are executed against source code (node_modules/histone/src/Histone.js) , to change that (testing custom build), pass --histone flag:
cd node_modules/histone/testnpm installnode test.js --histone=../Histone.js
If you want to run specific test, pass --suite flag:
cd node_modules/histone/testnode test.js --suite=MyTestSuite.js
Building production package from source code
When you need to use Histone in the web - browser, you can prepare special package in required format, first of all you'll need to install Node.js® version of Histone as it's described above, then do the following:
cd node_modules/histone/build/npm installnode build.js [options]
Where options are:
- --verbose - display messages during the build
- --exclude-parser - don't include Parser into production package
- --format=FORMAT - where FORMAT is one of the following:
- global - makes package where Histone is exported into the global variable
- amd - makes AMD - compatible package
- commonjs - just merges all original source files into one huge CommonJS module (default)
- --lang=LANGUAGE - where LANGUAGE is the path to the language file (see i18n folder for example)
- --defaultLang=LANGUAGE - specify language that will be used by default
- --target=PATH - path to target file (default will be node_modules/histone/Histone.js)
For example if you need Histone as AMD module with Russian and English languages you'll do the following:
npm install histonecd node_modules/histone/build/npm installnode build.js --format=amd --lang=../i18n/ru.js --lang=../i18n/en.js --defaultLang=ru
Production package build for the web - browser might look somewhat like this:
npm install histonecd node_modules/histone/build/npm installnode build.js --format=amd --lang=../i18n/ru.js --exclude-parser
Using template engine
When you connect Histone to your project (no matter if it's Node.js® or browser), you'll use it something like this:
var Histone = ;;
of course soon or late you'll want to load template from the file, in this case you'll have to set up resource loader which will be responsible for loading templates:
var FS = Histone = ; Histone; ;
Returning and passing raw JavaScript values from and to the template
Most of the time you'll use Histone for text generation, but sometimes it's necessary for the template to return some raw JavaScript value, Histone will do the conversion between internal data types and JavaScript data types for you:
'{{return /regexp/ig}}';
Same way you can pass any previously registered JavaScript object to the template's context:
'{{return this->isRegExp}}';
This trick works with any data type recognized by Histone, i. e. Histone.Undefined <-> JavaScript undefined, Histone.Null <-> JavaScript null and so on. Histone.Array will be automatically converted into JavaScript Array or JavaScript Object, depends on the array contents:
// return Array'{{return [1, 2]}}'; // return Object'{{return [foo: 1, bar: 2]}}';
Histone.Date will be automatically converted into JavaScript date, and vice versa:
// return JavaScript Date'{{return getDate}}'; // JavaScript Date will be automatically converted into HistoneDate'{{this->isDate}}';
Histone.Macro will be automatically converted into JavaScript function, but JavaScript function will never be converted into Histone.Macro when passed as template's context:
// return Function'{{return => 2 * 2}}'; // JavaScript function will never be converted into HistoneMacro'{{return this->isMacro}}';
Managing return type produced by render
If you need Histone data type instead of it's JavaScript equivalent as a result of template rendering, you can convert it using Histone.toHistone:
'{{return [1, 2, 3]}}';
or you can specify return type explicitly:
'{{return [1, 2, 3]}}';
Sometimes it's required to have string as a result of template rendering (for example you use rendering result as a server response in the web - application), obviously you can check and convert result yourself:
'{{return [1, 2, 3]}}';
or you can specify explicit stringification:
'{{return [1, 2, 3]}}';
Extending template runtime
You can assign new methods (or overwrite existing ones) to any of the Histone built - in data types, using Histone.register method:
Histone;
Where PROTOTYPE is one of the following:
- Histone.Base.prototype - base data type for any Histone value
- Histone.Undefined.prototype - Undefined data type
- Histone.Null.prototype - Null data type
- Histone.Boolean.prototype - Boolean data type (in fact JavaScript Boolean)
- Histone.Number.prototype - Number data type (in fact JavaScript Number)
- Histone.String.prototype - String data type (in fact JavaScript String)
- Histone.RegExp.prototype - RegExp data type (in fact JavaScript RegExp)
- Histone.Array.prototype - Array data type
- Histone.Date.prototype - Date data type
- Histone.Macro.prototype - Macro data type
- Histone.Global.prototype - Global object
Here are couple of simple examples that will give you some understading:
var Histone = ; // create global methodHistone; // test our brand new method;
Same thing goes for any other valid Histone data type, i. e. you can introduce new methods for Strings, Numbers, Arrays and so on, the idea is that if you miss something you just add it yourself:
var Histone = ; // create global methodHistone; // test our brand new method;
Third argument to Histone.register not necessary should be a function, it can be anything recognized by Histone, automatic conversion will be applied if needed:
var Histone = ; // method that returns stringHistone; // method that returns array, JavaScript array will be automatically converted into HistoneArrayHistone; // test it; // test it;
Returning self value
In some situations it's required for the method to return the value itself, usually you would solve it somewhat like this:
var Histone = ;// register String method which returns string itselfHistone;// myStringconsole;
This looks a bit stupid to create a whole function which does nothing but returning it's first argument. If you care about such things, use Histone.R_SELF instead of function in such situations:
var Histone = ;// register String method which returns string itselfHistone;// myStringconsole;
Handling arguments
Arguments to the called method are passed as an array in the second argument:
var Histone = ; // create global methodHistone; // test it;
Asynchronous methods
In order to support asynchronous processing, just define method handler as a function with at least 4 arguments:
var Histone = ; // method that waits 1 second and returns stringHistone; ;
Introducing new data types
Native JavaScript classes can also be easily introduced to Histone, let's create a wrapper for JavaScript Date object:
var Histone = ; // create global method which returns Date instanceHistone; // we have to register at least one method for the Date.prototype, so Histone can recognize itHistone; // test our Date wrapper;
Returning values from the templates
Once we've registered the type, we can return it from the template:
var Histone = ; // create global method which returns Date instanceHistone; // we have to register at least one method for the Date.prototype, so Histone can recognize itHistone; // test our Date wrapper;
Handling internal Histone data types
Note that when you extending Histone, your method handlers will have to deal with internal Histone data types, not their JavaScript equivalents:
var Histone = ; // create global method which returns true if it's argument is an arrayHistone; ;
If you don't care about internal Histone type, you can easily convert the value you receive in your method handler into native JavaScript value:
var Histone = ; // create global method which returns true if it's argument is an arrayHistone; ;
Second possibility is to explicitly specify desired argument type when registering method:
var Histone = ; // create global method which returns true if it's argument is a JavaScript DateHistone; // passing JavaScript Date, result = trueconsole;// passing Histone Date, result = trueconsole;
Most of Histone data types are simple references to the JavaScript data types, however it's recommended to use Histone.Type instead of it's JavaScript equivalent in order to be compatible with the future releases:
var Histone = ; // create global method which returns true if it's argument is a RegExpHistone; ;
Using Histone macros as callback functions
Sometimes you need to pass Histone macro as a callback in your custom method, then call it based on some condition:
var Histone = ; Histone; ;
Extending Histone data types
You can extend Histone data types and provide your own version of Array, Macro and so on, note that this won't work for the things like String, Number, RegExp and so on, because most of the current JavaScript engines won't allow you to extend native JavaScript objects, but this might change in the future, so for now it will really work only for Arrays and Macros:
var Histone = ; // create our version of Arrayvar { HistoneArray; };MyArrayprototype = Object;MyArrayprototypeconstructor = MyArray;MyArrayprototype { return 'MyArray.prototype.test'; }; // define some method for our version of Array, otherwise Histone won't recognize itHistone; // register global method wich will return MyArray instanceHistone; // test it;
Converting internal Histone data types into native JavaScript data types
When you're extending Histone and writing custom method handlers, you have to deal with internal Histone data types, however sometimes you don't need it and instead of working with internals, you prefer to convert them into JavaScript values, in this case there are couple of methods that you might find useful:
- Histone.toJavaScript - converts Histone value into a JavaScript value if converter is registered (see Histone.M_TOJS)
- Histone.toBoolean - converts Histone value into a JavaScript Boolean
- Histone.toString - converts Histone value into a JavaScript String
- Histone.toNumber - converts Histone value into a JavaScript Number
- Histone.toJSON - converts Histone value into a JSON String
var Histone = ; // register global method to test conversion utilsHistone; ;
Calling Histone methods internally
Sometimes it's necessary to perform method call internally (for example in the method handler when extending the engine), in this case you can use Histone.invoke method:
var Histone = ; Histone; ;
Btw this can be done even without templates, rendering and so on, check it out:
var Histone = ;// same as {{"string"->split}} in Histone templateconsole;
Arguments will be converted into Histone data types automatically:
var Histone = ;// JavaScript Array will be converted into HistoneArray automatically// result will be "1.2.3.4"console;
Calling methods of the Histone's Global object is also as simple as this:
var Histone = ;// Histone.global holds instance of Histone.Global.prototype// 1console;// 4console;
You might notice already that in some situations there is a callback function which receives the result of the method call, but sometimes Histone.invoke returns the result directly without callback, what's the difference? Basically the idea is following, if you know what you calling, and you sure that the method you call is synchronous, then use Histone.invoke without callback, otherwise always use Histone.invoke in it's asynchronous form. For example, it's known that Array->join is synchronous, so you can safely call it synchronously, but if that wouldn't be truth (or you don't know it) then call it asynchronously, that won't fail in any case:
var Histone = ;// call Array->join synchronouslyconsole;// call Array->join asynchronouslyHistone;
So for the information about built-in methods and preferred way to call them, refer to API documentation, 3rd - party documentation or just call them asynchronously if you've got no idea about them.
Handling return data types
By default, Histone.invoke (no matter whenever you call it synchronously or not) will return value represented as Histone data type, in most situations it's ok, but sometimes it's better to work with JavaScript data types, if that so, you have two possible solutions. First one is to convert the value returned by Histone.invoke using Histone.toJavaScript:
var result Histone = ;// Histone data typeconsole;// JavaScript data typeconsole;
To make it even shorter, here comes the second variant, specify return type explicitly:
var Histone = ;// JavaScript data typeconsole;
Calling method on prototypes
In some situations, when you're using native JavaScript prototype inheritance, you might need to be able to call method on the parent class / prototype (call super). For example, you've extended Histone.Array in order to make your own Array with blackjack and hookers:
var Histone = ; // create our version of Arrayvar { HistoneArray; };MyArrayprototype = Object;MyArrayprototypeconstructor = MyArray; // "override" Histone.Array->toString method with own implementationHistone;
Now you want to call Histone.Array->toString, in order to do that simply pass JavaScript - array, that consists of prototype and method name into Histone.invoke:
// "override" Histone.Array->toString method with own implementationHistone; // define Global method that will return instance of MyArrayHistone; // (SECRET_CONTENT);// very secret content;
Magic methods
Second argument to Histone.register most of the time represents name of the registered method as a string, however there are some MAGIC methods that is used in special cases:
- Histone.M_GET - called in case of attempt to read value's property, acts as property getter
- Histone.M_CALL - called in case of attempt to call value as a method
- Histone.M_TOJS - called when conversion to native JavaScript value is required
var Histone = ; { thisinternalObj = foo: 'bar' bar: 'foo' ;} // register property getterHistone; // register call handlerHistone; // register convertor to native JavaScript valueHistone; // register method that returns MyObject instanceHistone; ; ; ;
Converting templates into AST representation
Template source code is good when you're developing your project, but once you go in production, parsing templates from source code is unnecessary overhead, so before uploading production version of you project, you'll want to convert all your templates into intermediate AST form, which won't require parser and will allow templates to be executed way more faster. In this case you use getAST method wich will return AST tree for the specified template source code:
var Histone = ;var templateAST = ;
There are plenty of useful Node.js® modules, so you can figure out yourself how to process project's file set and convert all found ".tpl" files into AST representation using code snippet provided above.
Managing internal Histone cache
Results of call to Histone require, loadText, loadJSON method are cached by default, which means that the second call with the same argument will give you the same result as it was for the first time. It's good in the production (where nobody changes your templates), but it becomes a real problem in developement environment where template files are changed pretty frequent, so if you don't want to restart your server after each change in the template file you'll have to clear internal Histone cache on every change. In case if you don't want Histone to cache anything at all, simply turn off the cache before doing anything else:
Histone;
But in combination with great chokidar module and Histone.clearCache, you can solve it more cleaver way:
// watch for changes in tpl fileschokidar// clear Histone cache on change;
Performing network operations
Sometimes you need to load text - file, JSON - file or template in your JavaScript code the way you're doing in the template. Of course you can use Histone.invoke to do that, but there is more convenient ways of doing that: Histone.loadText, Histone.loadJSON and Histone.require. All of this methods are behaving exactly as they were called from the template or using Histone.invoke, it means that network request goes through the resource loader set by Histone.setResourceLoader and internal caching mechanism.
// load text file using template; // load text file using Histone.invokeHistone; // load text file using Histone.loadTextHistone;
When loading JSON - file, using Histone.loadJSON it's important to realize that result will always be converted into Histone data type:
// load JSON file using Histone.loadJSONHistone;
You can explicitly specify Histone.R_JS as desired result type:
// load JSON file using Histone.loadJSONHistone;
Same goes for require:
// load and process template using Histone.requireHistone;
If you explicitly specify Histone.R_JS as desired result type, then returned result will be converted into corresponding JavaScript value:
// load and process template using Histone.requireHistone;
If you need string, set Histone.R_STRING as desired result type:
// load and process template using Histone.requireHistone;