< back

Theme Engine

The StoreBuilder Theme Engine is an extremely powerful component which allows for 100% control over the entire website markup, javascript and css and enabling StoreBuilder websites to be extremely fast.

StoreBuilder uses handlebars for page templating plus regular old industry standard JavaScript and CSS (and can capitalize on popular frameworks like jQuery and Twitter Bootstrap).

Theme Folder Structure

Your web project should contain one root folder within which all themes reside. Each theme will have its own file folder for organization. The default theme name and therefore folder name is 'default'.

  • ~/themes
    • /default
      • files for 'default' theme are located here, optionally organized with more sub folders
    • /customtheme
    • /some-other-theme

There is no mandatory folder structure for themes, however we recommend organizing themes by common areas.

  • ~/themes
    • /default
      • /everywhere
      • /home
      • /listing
      • /product
      • /cart
      • /checkout
    • /customtheme
    • /some-other-theme

The physical file folder structure for a theme is primarily intended for developer convenience and organization and should not affect your production website functionality or performance.

All theme resources will be compiled/combined/minified/optimized according to their Theme Engine meta data, usually supplied via a comment header at the beginning of the file.

Theme File Headers

All text based theme files (.css/.js/.hbs) can optionally have a comment header added to them which will control how the StoreBuilder Theme Engine handles that file. There are some slight differences between handlebars template headers, javascript file headers and css file headers, but they all share a common structure of key-value pairs of meta information.

Meta data headers should always be the first comment in the file (feel free to leave any additional comments after this header). By requiring the Theme Engine meta data comment to be first we can make the compile/combine/minify process much faster as we do not read the entire contents of any theme files during discovery and dependency resolution. This allows us to go through more files (and larger files) in less time.

StyleSheet (*.css) File Header

StyleSheets use /* ... */ notation for comments. All key-value pairs are optional, however the default behaviour will be to ignore CSS files which are either missing the Theme Engine comment header or have incomplete header information.

/*
Compile-Minify: true
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: magnific
Compile-Dependencies: bootstrap,overrides
*/

JavaScript (*.js) File Header

JavaScript also uses /* ... */ notation for comments. All key-value pairs are optional, however the default behaviour will be to ignore JS files which are either missing the Theme Engine comment header or have incomplete header information.

/*
Compile-Minify: true
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: my-js
Compile-Dependencies: jquery,storebuilder,bootstrap
*/

Handlebars Template (*.hbs) File Header

Handlebars uses {{!-- ... --}} notation for comments. All key-value pairs are optional, however the default behaviour will be to ignore handlebars template files which are either missing the Theme Engine comment header or have incomplete header information.

{{!--
Compile-Minify: true
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: cart.minicompare
Compile-Dependencies: handlebars
--}}

Theme Meta Data Definitions

Here is the list of built in meta data key-value pairs which affect how StoreBuilder ThemeEngine will handle your theme files.

Compile: true(default)/false

The "Compile" key-value pair indicates whether this text based theme resource will be compiled (.hbs) and/or combined (.js/.css) for use on the public facing website. If a file has "compile:false" added to it, the file will be ingored when combining/minifying website resource, but it will still be available on the file system directly or for use on the server side (such as a handlebars template only intended for use server side).

Compile-Minify: true(default)/false

The Compile-Minify key-value pair indicates whether this text based theme resource should be minified during combination with other resources. The default is true, but can be overridden in the case that you are supplying a pre-minified .js/.css file supplied by a 3rd party. It may often be a waste of resources to re-minify and already minified resource.

Compile-Area: everywhere|listing|product|cart|checkout

The Compile-Area key value pair is simply a string value used to group text based theme files together for compiling/combining/minifying. You can use any Compile-Area you want (including making up your own area for some functionality on your website that is not StoreBuilder related), but we have identified a few common areas that all ecommerce websites will have: everywhere, listing, product, cart, checkout.

All files of the same type (.js/.css) with the same compile area will be considered for combination.

Compile-OutputGroup: headinline|bodyendtag|onload

The Compile-OutputGroup key value pair is used to further define how static resources are combined. One of the key techniques for speeding up websites is to defer loading of lower priority things and prioritize loading of important things such as "above the fold" content the user will see right away. You might group your javascript into "mandatory must run asap" scripts and "progressive enhancement run after the page has loaded" type scripts in order to reduce the amount of javascript that needs to be loaded, parsed and executed during page load.

In order to achieve a perfect score in Google Page Speed Insights you must pay close attention to which resources should be included within the page markup inline, which resources should be loaded at the very end of the document before the closing body tag, and which resources can be loaded after initial page load.

Compile-Exports: {alias}

The Compile-Exports key-value pair defines an alias for this resource file. This string value is used as the template name when compiling handlebars templates, and is also used for dependency resolution between theme files (sorting prior to combination) as well as duplicate file resolution for both mistakes and for theme inheritance (one theme can inherit from another theme and override one or more files).

Compile-Dependencies: {dependency-1}, {dependency-2}

The Compile-Dependencies key-value pair is a comma separated list of dependencies this file requires. Dependencies are referenced via their declared Compile-Exports value. The StoreBuilder Theme Engine will ensure that all static resources are output in the correct order given the declared dependencies for each file.

Theme File Preprocessors

Theme files are processed in a particular order when being compiled/minified. Most notably, all pre-processors execute before standard .js/.css combination/minification. This allows pre-processors to output files which may be included in the final bundled .js/.css.

When a pre-processor compiles a resource which results in .js/.css output, the original meta data header is preserved on the output of the pre-processor.

For example the handlebars compiler will run before the javascript combination/minification, and any meta data header on the original handlebars template file will be copied into the compiled javascript output for that template file. In this way when the javascript combiner/minifier runs it will pick up any compiled handlebars templates to be considered in any combined/minified .js bundles.

Special Output Groups

While there is no technical limit to what strings you can specify for Compile-OutputGroup we believe there are only 3 which are necessary, 2 of which have special behaviours.

Compile-OutputGroup: headinline specifies that this resource will be combined and minified with other files in the same Compile-Area but be kept in memory to be written out within page markup within the HTML <head> element. This is used to prioritize CSS styles that absolutely must exist before the page can be viewed, while deferring all other CSS to be loaded later.

Compile-OutputGroup: headinline specifies that this resource will be loaded/executed at the end of the page markup just before the closing </body> tag. This is the most common best practice for all JavaScript unless you are able to further optimize by deferring some javascript to load even a bit later if it isn't likely useful within the first 1/2 second or 2 when a user lands on your page.

Compile-OutputGroup: onload specifies that this resource will be combined and minified with other files in the same Compile-Area and is intended to be deferred until the page initially loads. By separating your javascript into high priority and progressive enhancement type groups, you can make the website feel much faster without actually changing much at all.

Example of Static File Combination

If we have the following two CSS files.

CSS_File1.css

/*
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: header_styles
*/
.header {
    margin-top: 15px;
}

CSS_File2.css

/*
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: footer_styles
*/
footer {
    background: #515151;
}

StoreBuilder's ThemeEngine will notice these two files have the same Compile-Area and Compile-OutputGroup tags and will therefore combine these two files together to produce an output file like this:

Sample file: everywhere_bodyendtag_F8447E6A9612C6593D2AE8FB5B6E4994.css

.header {
    margin-top: 15px;
}
footer {
    background: #515151;
}

Example of Dependency Resolution

If we have the following two JS files.

JS_File1.js

/*
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: config_vars
*/
var foo = 'bar';

JS_File2.js

/*
Compile-Area: everywhere
Compile-OutputGroup: bodyendtag
Compile-Exports: awesome_sitecode
Compile-Dependencies: config_vars
*/
if(foo === 'bar'){
    foo = 'baz';
}

StoreBuilder's ThemeEngine will first notice these two files have the same Compile-Area and Compile-OutputGroup tags and will therefore combine these two files together. But ThemeEngine will also notice that the second file has a Compile-Dependencies tag which matches a Compile-Exports tag from the first file. Therefore when combining these files together, it will ensure the ordering is correct starting with JS_File1.js and then appending JS_File2.js.

Sample file: everywhere_bodyendtag_F8447E6A9612C6593D2AE8FB5B6E4994.js

var foo = 'bar';
if(foo === 'bar'){
    foo = 'baz';
}

NOTE: dependency resolution was designed to be fault tolerant and will not fail complete if a dependency can't be resolved. It will resolve all dependencies that can be resolved within your theme and continue without any that can't be resolved. This allows allows you to note dependencies in your source files even if they won't be fulfilled by ThemeEngine such as if a 3rd party library will be included elsewhere in the site code. For example we often note jquery as a dependency regardless of whether jquery will be included in our bundled minified JS or as a separate script tag from another server such as a CDN.

Self Hosting 3rd Party Resources

This is an advanced technique of website optimization that should only be used under the direction of an expert level website optimizer. Most 3rd party services will explicitly state in their documentation that this configuration is not supported by them.

Often times 3rd party libraries are served with a very short cache duration specified, just in case that 3rd party needs to update their library at any time. Common examples include Google Analytics or any social media SDK such as Facebook or Twitter.

However, most website evaluation tools like Google Page Speed Insights will correctly note that the Google Analytics file is served with a short cache duration and deduct from your overall site corescore because of that. The idea is that files with short cache durations may have to be downloaded more times than necessary by your visitors and slow down your website some.

The ideal solution is to serve the static resource with a long cache durationduration, likesuch as 30 days, but then use a filename based cache buster when the contents of that file changes. StoreBuilder's ThemeEngine can do this for you.

Sample file for self-hosting google Analytics:

/*
Compile-Minify: false
Compile-Exports: analytics
Compile-SourceUrl: http://www.google-analytics.com/analytics.js
Compile-SourceHash: C889D7F1E7E37C77F20C434F5E268B60
*/

You will notice the new ThemeEngine tag Compile-SourceUrl and Compile-SourceHash. The Compile-SourceUrl tag is just as it looks, the url of the remote url you wish to self host. The Compile-SourceHash is an MD5 checksum of the source file provided by the 3rd party.

StoreBuilder will periodically check the remote server for changes to this file by comparing the current source file's MD5 checksum to the last saved version of that file locally. If the source file changed, it will download the source file and write its contents in the local file, updating the Compile-SourceHash tag with the new value.

Because 3rd party static resources are often served pre-minified, you'll notice we've also added Compile-Minify: false so that ThemeEngine doesn't try to re-minify the google analytics code.

We've omitted Compile-Area and Compile-OutputGroup in the above example for clarity. But you can quite easily combine and optionally minify multiple 3rd party resources together. All of these self-hosted 3rd party files will be kept up to date on your site and if any changes are made by those 3rd parties a filename based cache buster will be used to force your website visitors to use the new file (while still maintaining a 30 day cache policy for all files).

Including ThemeEngine Output Files in your Website

The StoreBuilder ThemeEngine class is located in the namespace StoreBuilder.Themes.ThemeEngine and exposes some static methods to easily get ThemeEngine assets.

public static string Get(string resourceType, string area) will retrieve the combined/minified bundle for any resource type (file extension) and Compile-OutputArea. If this method overload is used, it will assume Compile-OutputGroup tag value of BodyEndInline as the default recommended location for most static resources on most websites. This overload also has some special behavior in that it will decorate the output script tag with a data-onload html5 attribute that contains a comma separated value list of additional static resources that should be loaded after the initial page load is complete (deferred loading).

Sample output string:

<script type="text/javascript" src="/everywhere_bodyendtag_C9E18455153F82B6434F601C919C9CC9.js" data-onload="/everywhere_onload_E01991C4C42DF1D8A0B9693F74A5F825.js,/home_onload_82256C70243A06FBDD348D8D5EABAE71.js,/thirdparty_onload_F8447E6A9612C6593D2AE8FB5B6E4994.js"></script>

Notice in the above example that this script tag will load 1 JavaScript file. But if the storebuilder.js library is included in there, storebuilder.js will notice the data-onload html5 attribute and load the 4 additional files listed after that page has loaded.

To use this method in an ASP.net MVC project, you could create a handy HtmlHelper extension method like this:

public static HtmlString RenderScripts(this HtmlHelper htmlHelper, string area)
{
    return new HtmlString(StoreBuilder.Themes.ThemeEngine.Get("js", area));
}

and then use it in your Razor views like this:

@Html.RenderScripts("product")