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-ClientSide: true(default) | false

The "Compile-ClientSide" key-value pair indicates whether this text based theme resource will be pre-compiled (.hbs) and/or combined (.js/.css) for use on the public facing website (client-side). If a file has "Compile-ClientSide:false" added to it, the file will be ignored when combining/minifying website resources, 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 in server side code).

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 | headtag | 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.

Compile-Ignore: true(default) | false

The "Compile-Ignore" key-value pair indicates whether this text based theme resource should be ignored by ThemeEngine altogether. If the file has Compile-Ignore:true, then this file will be ignored for all purposes, server side rendering or client side compilation/combination/minifying, and/or theme inheritance (files with Compile-Ignore:true will not be inherited in derived themes.

Upgrade-Behavior: Backup(default) | Protect | Overwrite

The "Upgrade-Behavior" key-value pair indicates how theme resource should be handled when a newer version of the file is available (usually because you updated a StoreBuilder plugin).

  • Backup: Any existing theme file with the same filename will be backed up by renaming the file appending a timestamp to the filename (YYYY-MM-DD-hh-mm-ss). Then the new file version will be written to your current theme folder with the original filename.
  • Protect: Any existing theme file with the same filename which also has the 'Upgrade-Behavior: Protect' tag will be preserved and the new file will be ignored.
  • Overwrite: Any existing theme file with the same filename which also has the 'Upgrade-Behavior: Overwrite' tag will be overwritten and no backup of the old file will be made. The new file simply be written over the old 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: bodyendtag 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.

Theme Configuration File

You may optionally provide a web.config text file in the root of your theme folder to provide some configuration.

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="theme:isDebug" value="true" />
  </appSettings>
</configuration>

theme:isDebug if set to true, ThemeEngine will disable resource combination/minification and comment removal. This is useful for debugging your theme as it maintains the human readable development copies of all theme resources.

theme:baseUrl allows you specify a baseUrl for serving resources, can be used to specify a CDN URL in a pull configuration. In this case all resource file references will be outputted with this baseUrl.

theme:baseTheme allows you to specify a base theme from which to inherit.

Theme Inheritance

ThemeEngine supports inheritance within StoreBuilder themes. This means you could have one theme in folder 'ThemeB' inherit from the theme in folder 'ThemeA'.

If the folder is empty for 'ThemeB' which inherits from 'ThemeA' then you have created an exact copy of the original theme.

You can now override individual theme files by simply copying them into your derived theme folder. Theme engine evaluates requests for resources beginning in the current theme folder and then following up the inheritance chain, ignoring any duplicates up the inheritance chain.

In order to specify a parent theme from which to inherit, you simply add a web.config text file in the root of your theme with an app setting to indicate the baseTheme.

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="theme:baseTheme" value="BaseTheme" />
  </appSettings>
</configuration>

Performance: Lazy Loaded, Cached but watches for File Changes

ThemeEngine was designed to do as little work as possible during website start-up in order to minimize impact on first request response time. Nothing is loaded or evaluated until it is first requested (lazy loaded).

Once ThemeEngine has evaluated a particular request for resources (resolved all dependencies, combined and minified files) that result is then cached so that subsequent requests for the same resources will be very fast.

ThemeEngine will watch for file system changes and invalidate cache entries when theme files are modified. For example if you FTP up a new CSS file, theme engine will clear its cache so that the next request for theme resources will re-evaluate and re-cache with the newest file changes. This means you can edit StoreBuilder Theme's using any favourite editor you wish Visual Studio, Notepad++, or plain old notepad. There is no need for any special processes to update the website, just save the file in place or ftp it back to your server and your changes will be live.

In order to further minimize the possibility of doing any work twice, ThemeEngine will cache intermediate output files from pre-processors such as the Handlebars template compiler. For example if you have a file like template1.hbs it will be compiled to template1.js by the handlebars compiler and a ThemeEngine tag Compile-SourceHash will be added to the output file that contains an MD5 hash of the source handlebars template file. If the ThemeEngine cache is invalidated for any reason and ThemeEngine begins re-evaluating output for some resources containing this template, it will first check if there were actually any changes to this source template before recompiling. If there were no changes to the source template file then there is no need to recompile the handlebars template and ThemeEngine can return quickly.

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 score 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 duration, such 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")