WILLIAM DROL

Front-End Architecture for Enterprise-Level Development

April 9th 2016

This is a comprehensive front-end architecture for enterprise-level development. Although primarily intended for component-based CMS platforms such as Sitecore, this architecture can support any web project with or without a CMS (it's flexible and can adapt to your needs). This document has four parts and should be read in order (each part builds on the previous):

  1. Best Practices for Front-End Components
  2. Building a Highly-Scalable Front-End Architecture
  3. Front-End Automation Guidelines and Work Flow
  4. Managing Component Dependencies and Refactoring

Features:

Dependencies:

View the Single Page Overview PDF:

Overview Diagram

PART 1: Best Practices for Front-End Components

Components are the building blocks of large, manageable applications and perform a key role in front-end development. Whether you're coding for Sitecore, Umbraco, some other CMS, or no CMS at all, front-end components normally require three files:

  1. The markup file (html, view, cshtml, etc.)
  2. The style file (css, less, sass, etc.)
  3. The javascript file (plain js, jQuery, etc.)

Each component provides a single unit of functionality (does one thing and does it well). Some components may not need a javascript file, that's OK. In typical applications, there are also server-side files associated with any given component, but I'll only be focusing on front-end files during these discussions.

Front-End Component Structure

In any large system, consistent and predictable naming is essential to long-term success. In back-end development, compiled languages such as Java and C# use explicit namespaces to build large, manageable hierarchies of reusable code. You can enjoy the same benefits in the front-end by choosing names that reflect your desired namespace hierarchy. The advantages are:

Suppose you're building a breadcrumbs component. You'll start with three files (notice they all share the same name):

  1. Breadcrumbs.cshtml
  2. Breadcrumbs.less
  3. Breadcrumbs.js

Here's an example folder hierarchy used to store the files:

Views
  Components
    Navigation
      Breadcrumbs
        Breadcrumbs.cshtml
        Breadcrumbs.less
        Breadcrumbs.js

With these steps, you've started a naming structure that can scale to many components across a large system. The important takeaway is to ensure that all files in each component share the same name and fit into a consistent folder hierarchy that reflects the desired namespace scheme in your project.

Unique Component Names

Each file in the component has a top-level container to support your intended namespace. Here's the Breadcrumbs.cshtml file:

<div class="Navigation-Breadcrumbs">
  <!-- The rest of Breadcrumbs.cshtml goes here -->
</div>

The class in the outermost container div follows the GroupFolder-ComponentFolder naming structure. The group folder is Navigation and the component folder is Breadcrumbs, so the resulting container class is Navigation-Breadcrumbs. The outermost container div is the component namespace container. Each component must have one and it must be unique. Because you're borrowing real folder names from the file system, there's no chance of naming conflicts across components, provided you follow the same pattern across all components.

Use Pascal casing to further distinguish this class as a unique component container. All other CSS classes inside the component should use lowercase as normal.

Similar case with the Breadcrumbs.less file:

.Navigation-Breadcrumbs
{
  //----- The rest of Breadcrumbs.less goes here
}

Once again, the outermost container ( .Navigation-Breadcrumbs ) matches the GroupFolder-ComponentFolder naming structure and the Pascal casing. Inside these brackets, you'll include whatever styles necessary to support the markup in the Breadcrumbs.cshtml file, but nothing else. The styling for each component should deliver only enough to support itself.

I'm using LESS files, but you can substitute SASS if you like.

Finally, the Breadcrumbs.js file. Notice again, the dedicated namespace variable matches the GroupFolder-ComponentFolder name. This file should encapsulate behavior for this component only, nothing else:

function Component()
{
  var namespace = '.Navigation-Breadcrumbs';

  this.init = function()
  {
    //----- Handle something
    jQuery( 'some-element-selector' , namespace ).click( function()
    {
      //----- Do something
    });

    //----- Handle something else
    jQuery( 'another-element-selector' , namespace ).click( function()
    {
      //----- Do something else
    });
  };
};

//----- When page loads
jQuery( document ).ready( function()
{
  new Component().init();
});
Responsive Best Practices

The component naming guidelines presented above support responsive design best practices. Notice the words "mobile, tablet, and desktop" are not present in the code or the file system. That's intentional and should always be your goal. Strive to use class names that are generic, reusable and intuitive, but also device and presentation agnostic. Good examples might be: class="product-list" and class="product" – these clearly describe the intent without being implementation specific.

In the breadcrumb example above, please be aware that you are NOT building three separate breadcrumb components to support the diverse needs of desktop, tablet, and mobile. A single breadcrumb component is sufficient to meet all requirements – you'll use CSS media queries to handle responsive styling inside the component.

CMS Best Practices

In CMS-based applications, any given page might contain one or more instances of any given component, so please be mindful with CSS classes and ids. Best practice is to prefer CSS classes instead of ids to avoid invalidating the page with duplicate ids at runtime. There are reasonable exceptions, such as master layouts, when you may occasionally want to use CSS ids instead of classes. For instance, a master layout might have id="site-global-header" or id="site-main-content" – this is perfectly valid because you know in advance these ids will be unique per page (all components will render inside such structures). Other than that, use CSS classes instead of ids.


PART 2: Building a Highly-Scalable Front-End Architecture

This architecture prefers and recommends components that are truly isolated from one another (there could be exceptions of course). Best practices for component structure and isolation were covered in Part 1, so if you haven't read that yet, please do so at this time.

In a modular CMS such as Sitecore, it's possible (and desirable) to build libraries of independent components for dynamic use on any page, so I recommend component isolation whenever possible. But you may not be using a CMS at all. That's fine. Either way, this architecture can scale to hundreds of components across thousands of pages without manually creating a dependency graph. Although it presumes that components are normally independent, by the end of Part 4, you'll also see how to write components the depend on each other (athough this is normally the exception instead of the rule).

Keep It Simple

This architecture respects the "keep it simple" principle and recognizes that lean building blocks are essential in large-scale systems. From my own experience using this architecture, I've already cut features or simplified it wherever possible. Perceived needs are not necessarily hard requirements, so potential nice-to-have features have been cut. Coding has been kept to a minimum and each line has been simplified as much as possible.

Example Scenario

You might choose this architecture on an enterprise-level marketing site with a large amount of deep content. In such a site, most components present read-only information, with little or no user interaction. No doubt there will be interactive components too (user registration, newsletter sign-up, etc.), but they normally won't need direct communication with each other (or other components) to function properly.

Other Scenarios

Other scenarios might be more complex, or more interactive and may require explicit component dependencies. That's fine, I'll also cover dependency management later in this document to handle cases like that, should they arise.

IIFE Closures vs. CommonJS and Browserify

In Javascript, immediately-invoked function expressions (IIFE; aka static scoping) ensures code is isolated and does not pollute the global variable space. Code wrapped in an IIFE closure cannot access code outside the closure. Everything inside the closure is locally scoped. This is how you might code a jQuery component using IIFE:

;( function( $ ) {

  //----- Block of code here...

})( jQuery );

This is a popular and valid approach, but I'd like to choose a different path for this architecture. We'll use CommonJS code patterns and Browserify to ensure isolated, locally-scoped components (just like IIFE), but with the option to define component dependencies and communication between components if needed. This works great even if we have no component dependencies at all.

Browserify

Browserify extends Javascript with two features named "exports" and "require" to simplify dependency management across JS components. Browserify is an automation tool that bundles JS files (more details in Part 3) to ensure that each component is 100% locally scoped, while also allowing communication with other components if needed.

The Javascript code presented below is not intended to run all by itself. In Part 3, we'll use Browserify to bundle front-end components for runtime use – bundling is not the same as minifying.

All components start out looking something like this (notice the public and private variables inside the component definition):

//----- Define state for this component
function Component()
{
  //----- Private scope is normal
  var myPrivateVariable1 = 'foo1';
  var myPrivateVariable2 = 'foo2';
   
  //----- Anything defined with 'this' is public
  this.myPublicVariable = 'something';
  this.myPublicFunction = function()
  {
    //---- do something
  };
};

Following the breadcrumb example from Part 1, you'll first add a namespace variable that follows the GroupFolder-ComponentFolder naming structure (every component must have one that's unique):

//----- Define state for this component
function Component()
{
  var namespace = '.Navigation-Breadcrumbs';
};

Next, add any additional code, perhaps code that runs on page load:

//----- Define state for this component
function Component()
{
  var namespace = '.Navigation-Breadcrumbs';

  this.init = function ()
  {
    //----- Handle something
    jQuery( 'some-element-selector' , namespace ).click( function()
    {
      //----- Do something
    });

    //----- Handle something else
    jQuery( 'another-element-selector' , namespace ).click( function()
    {
      //----- Do something else
    });
  };
};

//----- When page loads
jQuery( document ).ready( function()
{
  new Component().init();
});

The code should handle requirements for this component only. Nothing else. Notice this line of code:

jQuery( 'some-element-selector' , namespace ).click( function()

When dealing with components, you should use the ( selector , namespace ) pattern to access elements in the page's DOM. This instructs jQuery to look for a match starting with the given namespace variable (in this case .Navigation-Breadcrumbs). Basically, you're limiting jQuery requests to instances of this component only. For the selector part, any valid selector expression is acceptable (i.e. '.main-content .product .product-details' ). Although there are many ways to work with jQuery, the important concept is to represent the namespace to avoid collisions with other components.

Managing Component Scope

The Javascript code presented above enforces local scope with no dependencies (provided all components follow the same pattern). Now you can apply the same local scope to the markup and styles:

<!-- Breadcrumbs.cshtml File -->
<div class="Navigation-Breadcrumbs">
  <!-- The rest of Breadcrumbs.cshtml goes here -->
</div>
//----- Breadcrumbs.less File
.Navigation-Breadcrumbs
{
  //----- The rest of Breadcrumbs.less goes here
}

By using .Navigation-Breadcrumbs as the top level container in each file, you ensure the component is locally scoped and runs in isolation. As presented in Part 1, because you're using real folder names directly from the file system, you can guarantee zero naming conflicts across components. The result is a comprehensive namespace system for the front-end:

Views
  Components
    Navigation
      Breadcrumbs
        Breadcrumbs.cshtml
        Breadcrumbs.less
        Breadcrumbs.js

Here's the bare minimum code for the three files in the breadcrumbs component. Each component should mimic these three files like a template:

<!-- Breadcrumbs.cshtml File -->
<div class="Navigation-Breadcrumbs">
  <!-- The rest of Breadcrumbs.cshtml goes here -->
</div>
//----- Breadcrumbs.less File
.Navigation-Breadcrumbs
{
  //----- The rest of Breadcrumbs.less goes here
}
//----- Breadcrumbs.js File
function Component()
{
  var namespace = '.Navigation-Breadcrumbs';

  //----- The rest of Breadcrumbs.js goes here
};

To be clear, the word "Component" as it appears in "function Component(){...}" above is literally meant to be coded that way. Component is not just a placeholder name that you'll later replace with something more specific. The word "Component" in this context has no special meaning other than to accurately describe its generic purpose. This will become more relevant and apparent in Part 4.

Expanding the Folder Structure

So what does the folder structure look like as you add more components? In the beginning, maybe something like this (notice the folder structure follows the same pattern as the Breadcrumbs component). This structure can scale as large as needed because the naming conventions are based on real folders in the file system.

Views  
  Components

    Callouts
      BlockQuote
        BlockQuote.cshtml
        BlockQuote.less
        BlockQuote.js
      PullQuote
        PullQuote.cshtml
        PullQuote.less
        PullQuote.js

    Layouts
      MainLayout
        MainLayout.cshtml
        MainLayout.less
        MainLayout.js

    Lists
      MenuList
        MenuList.cshtml
        MenuList.less
        MenuList.js
      TreeList
        TreeList.cshtml
        TreeList.less
        TreeList.js

    Navigation
      Breadcrumbs
        Breadcrumbs.cshtml
        Breadcrumbs.less
        Breadcrumbs.js
      Pager
        Pager.cshtml
        Pager.less
        Pager.js

    Media
      Audio
        Audio.cshtml
        Audio.less
        Audio.js
      Image
        Image.cshtml
        Image.less
        Image.js
      Video
        Video.cshtml
        Video.less
        Video.js

    Structures
      OneColFixed
        OneColFixed.cshtml
        OneColFixed.less
        OneColFixed.js
      OneColFluid
        OneColFluid.cshtml
        OneColFluid.less
        OneColFluid.js
      TwoColEqual
        TwoColEqual.cshtml
        TwoColEqual.less
        TwoColEqual.js
      TwoColSidebarLeft
        TwoColSidebarLeft.cshtml
        TwoColSidebarLeft.less
        TwoColSidebarLeft.js
      TwoColSidebarRight
        TwoColSidebarRight.cshtml
        TwoColSidebarRight.less
        TwoColSidebarRight.js

Up to this point, we've only covered zero-dependency components. Finish Parts 3 and 4 to handle components that depend on other components.


PART 3: Front-End Automation Guidelines and Work Flow

The Javascript code presented in Part 2 is not intended to run all by itself, so we'll use Browserify and some automation tools to bundle the code for deployment and final runtime use. If you haven't read Part 2 yet, please do so at this time.

So Many Files, Not Enough Time

The following sections describe how to automate front-end deployments in a repeatable, continuous work flow. Each component requires two or three files on the front-end – on a major project that means dealing with a large number of related files.

Automation lets you manage and deploy hundreds of components with ease and repeatability. Once the automation is setup and working, you won't need to think about it very much in your daily routine. My front-end automation system delivers the following:

Source Code vs. Deployed Applications

Large team-based development efforts (whether CMS or otherwise) typically make a distinction between raw source code and deployed applications. Source code is the version-controlled set of files (via GIT, Subversion, etc.) that each developer on the team works with. Deployed application code is the compiled, minified, runtime version of the application that's deployed somewhere else. Depending on your role, deployed code might mean your local file system or maybe a central QA server.

The important distinction is that raw source code is typically NOT the code base you're reviewing in your browser at runtime (source code is separate from deployed code). Most large-scale systems, Sitecore included, recommend this as best practice.

This brings up an important point about choosing front-end automation tools. Our front-end work flow must support both paths in the file system (source vs. deployed). In other words, no matter what method we use to edit/debug/update our raw source code, we need to ensure our deployed application updates automatically (no worries, it will).

Although there are some great tools out there to manage front-end files, they often make the assumption that raw source code is the same as a deployed application. Even if that weren't an issue, I still haven't seen a single product in Windows that delivers 100% of the requirements, which is why I'm not recommending a turn-key automation product. Instead, let's use a flexible open source task runner (such as Gulp or Grunt) that can handle anything.

Gulp or Grunt?

To help automate front-end builds, two popular task runners come to mind: Gulp.js and Grunt.js. Both are open source with an active developer community. Grunt prefers configuration over coding and excels at static tasks. Gulp prefers coding over configuration and excels at dynamic tasks.

We can achieve the same with either task runner, so best fit mostly depends on personal preference. I feel like Gulp is a natural fit for JS developers, but if you prefer Grunt instead, please feel free to adapt the concepts below as needed.

Gulp Automation

Getting started with Gulp requires Node.js and the Node Package Manager (npm). I recommend the following setup instructions and files. They're fully compatible with this architecture and ensure that you're running npm version 3.x or greater (which fixed some nasty Windows bugs):

Setup Instructions and Files for Node and Gulp:  http://is.gd/q8u4Eo

Here's part of the example folder structure. When you finish the setup at the link above, you'll have 7 additional files:

FrontEnd
  Libs
  Scripts
    website.min.js
    website.min.js.map
  Styles
    website.min.css
    website.min.css.map
Views
  Components
    Layouts
    etc...

Gulpfile.js
Gulpsetup.cmd
package.json

At this point, your daily front-end routine should be as easy as opening a command window at the root of your site and entering the following command. If you're running Visual Studio 2015, you can bypass the command line entirely and use the built-in Task Runner Explorer instead.

gulp  (run default tasks and enter watch mode)

You can edit front-end source files in Visual Studio or any text editor. To edit source files directly in Chrome (which makes for a more immersive editing experience with instance feedback) please see Step 10 in the setup instructions that you opened above. Chrome advantages are:

Your daily front-end routine should be pretty minimal at this point. Gulp watches for source code changes, bundles, minifies and copies files to your web deployment target as needed. It ensures your front-end source code stays in sync with the deployed site, which means no surprises to your version control system.


PART 4: Managing Component Dependencies and Refactoring

Up to this point, the example code has focused on components with zero-dependencies. That's still my primary recommendation (especially in a dynamic CMS like Sitecore). With a few simple changes, however, you can also support components that depend on other components. Basically, to make something in a component available for public consumption, use the exports feature:

module.exports = object;

To consume this in another component, use the require feature:

var myObjReference = require( 'some-reference' );
var myObjInstance  = new myObjReference();

For example, suppose you have these two components:

  1. Product Dashboard Component
  2. Featured Products Component

And you want featured products to interact with product dashboard. Here's ProductDashboard.js:

//----- Define state for this component
function Component()
{
  var namespace = '.Products-ProductDashboard;

  this.update = function()
  {
    alert( 'update ' + namespace );
  };
};

//----- Make this component available to others
module.exports = Component;

And here's FeaturedProducts.js:

//----- Use other components
var DashboardClass = require( '../Products/ProductDashboard/ProductDashboard.js' );

//----- Define state for this component
function Component()
{
  var namespace = '.Products-FeaturedProducts';
  var dashboard = new DashboardClass();

  this.init = function()
  {
    jQuery( namespace ).click( function()
    {
      dashboard.update();
    });
  };
};

ProductDashboard.js exports the entire component so that other components can interact with it:

//----- Make this component available to others
module.exports = Component;

FeaturedProducts.js requires (uses) ProductDashboard.js by passing a relative file path:

var DashboardClass = require( '../Products/ProductDashboard/ProductDashboard.js' );

Then it creates a dashboard instance to use as needed:

var dashboard = new DashboardClass();
dashboard.update();

In this example, ProductDashboard.js has a single public function named update, but the public interface could certainly be more elaborate if needed. The dashboard instance in FeaturedProducts.js has access to any public functions and variables (anything defined with the keyword this).

To be clear, the word "Component" as it appears in "function Component(){...}" above is literally meant to be coded that way in ALL components (it's not just a placeholder name that you'll later replace with something more specific). The Browserify automation covered in Part 3 ensures that each component is safely isolated and does NOT land in the global variable space. The word "Component" in this context has no special meaning other than to accurately describe its generic purpose. As stated elsewhere, it's really the "namespace" variable that truly reflects the identify of any given component (more specifically, the "Folder-Component" value stored inside the "namespace" variable).

Here's the minimum code for an exportable (consumable) component:

//----- Define state for this component
function Component()
{
  // Anything defined with 'this' is public. Everything else is private.
  // this.myVariable = 'something';
  // this.myFunction = function(){ /* do something */ };
};

//----- Make this component available to others
module.exports = Component;

Here's the minimum code for a component that consumes something:

//----- Use other components
   var MyClass    = require( './MyComponent.js' );
// var Something1 = require( './Something1.js'  );
// var Something2 = require( './Something2.js'  );
// var Something3 = require( './Something3.js'  );

//----- Define state for this component
function Component()
{
  //----- Various state stuff

  //----- Use instance of MyClass
  var myObj = new MyClass();
  myObj.myFunction();

  //----- Etc...
};

Here's the full example code for ProductDashboard.js:

//----- Define state for this component
function Component()
{
  var namespace = '.Products-ProductDashboard';

  this.init = function ()
  {
    alert( 'init ' + namespace );
  };

  this.update = function()
  {
    alert( 'update ' + namespace );
  };
};

//----- Make this component available to others
module.exports = Component;

//----- When page loads
jQuery( document ).ready( function()
{
  new Component().init();
});

Here's the full example code for FeaturedProducts.js:

//----- Use other components
var DashboardClass = require( '../Products/ProductDashboard/ProductDashboard.js' );

//----- Define state for this component
function Component()
{
  var namespace = '.Products-FeaturedProducts';
  var dashboard = new DashboardClass ();

  this.init = function()
  {
    jQuery( namespace ).click( function()
    {
      dashboard.update();
    });
  };
};

//----- Make this component available to others
module.exports = Component;

//----- When page loads
jQuery( document ).ready( function()
{
  new Component().init();
});
Refactoring Javascript

As the code base grows, it's inevitable you'll experience some repetitive code. If you're comfortable writing jQuery plug-ins, that's an excellent way to support reuse. But you can go even simpler by using the requires and export features from the previous section.

Basically, you'd create a code-only component (no markup or css) named something like MyProjectGlobalLibrary. Remember to replace "MyProject" with the real name of your project in all of the source code that follows. Here's the start of the library:

//----- MyProjectGlobalLibrary.js
//----- Define state for this component
function Component()
{
  this.myUtilityFunction = function()
  {
    //----- myUtilityFunction code goes here
  };

  this.myOtherUtilityFunction = function()
  {
    //----- myOtherUtilityFunction code goes here
  };
};

//----- Make this component available to others
module.exports = Component;

In the example folder structure, the new file would go here:

FrontEnd
  Build
    Gulp
  Deploy
    Libs
    Scripts
    Styles
Views
  Components
    Callouts
      BlockQuote
      PullQuote
    Global
      MyProjectGlobalLibrary
        MyProjectGlobalLibrary.js
    Layouts
      MainLayout
    Lists
      MenuList
      TreeList
    Navigation
      Breadcrumbs
      Pager
    Media
      Audio
      Image
      Video
    Structures
      OneColFixed
      OneColFluid
      TwoColEqual
      TwoColSidebarLeft
      TwoColSidebarRight

Calling library functions from other components goes like this:

//----- Use other components
var LibraryClass = require( '../../Global/MyProjectGlobalLibrary/MyProjectGlobalLibrary.js' );

//----- Define state for this component
function Component()
{
  var namespace  = '.Navigation-BreadcrumbNavigation';
  var libraryObj = new LibraryClass();

  this.init = function ()
  {
    //----- Handle something
    jQuery( 'some-element-selector' , namespace ).click( function()
    {
      libraryObj.myUtilityFunction();
    });

    //----- Handle something else
    jQuery( 'another-element-selector' , namespace ).click( function()
    {
      libraryObj.myOtherUtilityFunction();
    });
  };
};

//----- When page loads
jQuery( document ).ready( function()
{
  new Component().init();
});
Refactoring Styles

So what about your styles, can those be refactored too? Yes, as long as you're using LESS or SASS instead of plain vanilla CSS. In fact, I cannot imagine working on a large project without a CSS pre-processor. If you're interested in stylesheet refactoring, please check out LESS mixins at lesscss.org (SASS has similar features at sass-lang.com).

Related Topics

That concludes my front-end architecture for enterprise-level development. I hope you've enjoyed it and find it helpful on large-scale projects. Here are some related links you may find useful: