HansS / skeletor-degdigital

A Pattern Lab-friendly front-end web project boilerplate and build tool

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Skeletor: Bones & Build Tools

Skeletor is a Grunt-powered, Pattern Lab-centric, highly-customizable web project boilerplate and build tool created by the DEG UI team. Skeletor uses PostCSS for CSS processing and JSPM/SystemJS for Javascript package management, module bundling/loading, and transpilation.

Requirements

  • NodeJS version 4.2+
  • PHP version 5.3+ (for Pattern Lab)

Installation Instructions

  1. Clone the Skeletor git repository into your project's root directory or a subdirectory
  2. From the command line, type npm install to install all Node dependencies
  3. From the command line, type grunt to generate your first Pattern Lab build

Atomic Design & Pattern Lab

Skeletor encourages the use of the Atomic Design methodology and comes preconfigured with Pattern Lab to help you create atomic design systems. Learn more about Atomic Design and Pattern Lab here.

In Skeletor, all primary front-end development occurs in Pattern Lab. You can then export your code as a standalone site or into a CMS. Read more about this in the workflows section.

Themes

Skeletor is a multi-theme build tool. A theme is considered a variation of a base website or set of assets. In a multi-theme environment, you would have a base theme (website) and one or more child themes that vary from the base theme in some way. Skeletor is configured for one theme by default but can easily be configured for multiple themes.

One benefit of using themes is the ability to have child themes inherit CSS from its base theme. This avoids repetition of code while still allowing for the overriding of CSS in a child theme when necessary.

Directory Structure

Skeletor can be the entirety of your web project or live side-by-side with your CMS source directory, depending on your needs. The default directory structure is as follows:

skeletor       
|-- config/  
|-- core/
|-- export/
|   |-- css/
|   |-- images/
|   |-- js/
|   |-- patterns/
|-- grunt/
|-- public/
|   |-- css/
|   |-- images/
|   |-- js/
|   |-- patterns/
|-- source/
|   |-- css/
|   |-- images/
|   |-- js/
|   |-- _patterns/
|-- Gruntfile.js

Pattern Lab Directories

By default, Pattern Lab lives in the root of Skeletor. The core/ and config/ directories contain the Pattern Lab source code and configuration settings, respectively. The source/ directory contains your project's source code, including pattern templates, CSS, JS, and images. The public/ directory contains the generated Pattern Lab site. Assets are compiled and copied from the source/ directory to the public/ directory during the build process.

Export Directory

The export/ directory contains production-ready assets that have been exported from Pattern Lab. Depending on the configuration of your project, many assets may bypass this directory and instead be exported into CMS directories.

Grunt Directory

Skeletor is powered by the Grunt task runner. Task configuration files can be found in the grunt directory. However, the majority of your configuration changes should be confined to the Gruntfile.

Multi-Theme Directory Structure

To use multiple themes with Skeletor, the directory structure changes slightly. A directory for each theme is added to the root directory of Skeletor. The assets for a theme live within the theme's directory. In addition, each theme has its own instance of Pattern Lab. This is due to the current limitations of representing multiple websites in a single instance of Pattern Lab.

An example of a multi-theme directory structure is below:

skeletor   
|-- baseTheme/
|   |-- config/  
|   |-- core/
|   |-- export/
|   |-- public/
|   |-- source/
|-- grunt/
|-- theme1/
|   |-- config/  
|   |-- core/
|   |-- export/
|   |-- public/
|   |-- source/
|-- theme2/
|   |-- config/  
|   |-- core/
|   |-- export/
|   |-- public/
|   |-- source/
|-- Gruntfile.js

Workflows

Skeletor supports two primary workflows: project development and exporting/CMS integration. Both workflows are controlled via the command line by invoking Grunt tasks.

Project Development Workflow

Primary development occurs within Pattern Lab, specifically the source directory. UI assets such as patterns, CSS, Javascript, and images are processed, compiled, and copied to the public directory by invoking the build task grunt build. You can run the build task automatically when source files are created/modified by invoking the listen task: grunt listen.

Exporting/CMS Integration Workflow

Pattern Lab outputs a static website. Depending on the nature of your project, this deliverable may be sufficient. In other cases, you may need to integrate your UI assets (HTML, CSS, JS, images, fonts, etc) into a production CMS. Either way, your UI assets are exported to directories of your choosing by invoking the export task grunt export.

Multi-Theme Targeting

The build, listen, and export tasks are created as multi tasks in Grunt. When Skeletor is configured for multiple themes, it automatically creates targets for each of these multi tasks, one target per theme. This allows you to constrain the tasks to one theme by appending :[themeName] when running a task from the command line.

For example, to run the build task on theme1, you would type grunt build:theme1 into the command line. To run the build task for all themes, simply omit the theme name postfix and type grunt build.

Configuration

Skeletor is built to be highly-configurable. The majority, if not all, of the configuration settings exist in the Gruntfile.

Theme Configuration

The majority of Skeletor's configuration settings are contained within theme configuration objects. These objects live within the themes object in the Gruntfile. By default, Skeletor is configured for one theme:

/* Themes configuration */
var themes = {
    /* Theme configuration */
    default: {
        basePath: '',
        ...
    }
};

For multiple themes, additional theme configuration objects can be included in the themes setting :

/* An example of two themes that inherit from a base theme */
/* Themes configuration */
var themes = {

  /* Base theme configuration */
  baseTheme: {
    ...
  },
  
  /* Theme 1 configuration */
  theme1: {
    parentTheme: 'baseTheme',
    ...
  },
  
  /* Theme 2 configuration */
  theme2: {
    parentTheme: 'baseTheme',
    ...
  }
};

Theme Defaults

Skeletor has a single theme configuration object that contains default theme settings. This object, called themeDefaults in the Gruntfile, gets merged with each theme configuration object in themes. This allows you to centralize common theme settings into one object while still retaining the ability to override certain settings in specific themes.

  var themeDefaults = {
    css: {
      exportSourceFiles: false,
      enableGlobbing: true
    }
  };
  
  var themes = {
    theme1: {
      css: {
        /* Overrides the exportSourceFiles setting inherited from themeDefaults */
        exportSourceFiles: true
      }
    }
  }

Theme Configuration

A theme configuration object contains settings for a particular theme.

General

theme1: {
    basePath: 'theme1',
    parentTheme: 'baseTheme',
    ...
}
basePath

Type: String
The base filepath of the theme, relative to Skeletor's root directory. If this property is omitted, the base filepath defaults to the name of the theme itself.

parentTheme

Type: String
The name of the theme's parent theme. Setting a parent theme allows for CSS inheritance.

Asset Directories

There are three primary directories that your assets live in: source/, public/, and export/ (see the Directory Structure section for more information). The file paths and urls for these asset directories are pre-configured for you:

/* Source directory configuration */
theme1: {
    'source': {
      basePath: 'source',
      assetPaths: {
        api: 'api',
        css: 'css',
        fonts: 'fonts',
        images: 'images',
        js: 'js',
        patterns: '_patterns',
        patternData: '_data'
      }
    },
    
    /* Public directory configuration */
    'public': {
      basePath: 'public',
      assetPaths: {
        api: 'api',
        css: 'css',
        fonts: 'fonts',
        images: 'images',
        js: 'js',
        patterns: 'patterns'
      },
      assetUrls: {
        js: '../../js'
      }
    },
    
    /* Export directory configuration */
    'export': {
      basePath: 'export',
      assetPaths: {
        api: 'api',
        css: 'css',
        fonts: 'fonts',
        images: 'images',
        js: 'js',
        patterns: ''
      },
      assetUrls: {
        js: 'js'
      }
    },
    ...
}
{assetDirectory}.basePath

Type: String Default: [assetDirectory]
The base file path of the asset directory relative to the theme's directory.

{assetDirectory}.assetPaths

Type: Object
The file paths of asset types (CSS, Javascript, images, etc) relative to the asset directory.

{assetDirectory}.assetUrls

Type: Object
The URLs of asset types relative to the web root. These settings currently only apply to the Javascript assets in the public and source asset directories.

CSS

Skeletor employs PostCSS to process your CSS files. There are several CSS configuration options available to you:

theme1: {
    /* CSS processing configuration */
    css: {
    
      /* Export CSS source files along with compiled files */
      exportSourceFiles: false,
    
      /* Enable CSS globbing */
      enableGlobbing: true
    },
    ...
}
css.exportSourceFiles

Type: Boolean Default: false
In addition to the processed CSS files, source CSS files are copied to the export directory during an export.

css.enableGlobbing

Type: Boolean Default: true
CSS @import statements are generated automatically using globbing.

Javascript

By default, Skeletor uses JSPM as a Javascript package manager, module bundler, module loader, and transpiler. There are several Javascript configuration options available to you in the Gruntfile:

/* Javascript processing configuration */
theme1: {
    js: {

      /* Processor for Javascript [jspm, none] */
      processor: 'jspm',  
    
      /* When to minify Javascript [always, never, exportOnly] */
      minify: 'exportOnly', 
    
      /* Enable module bundling for use with JSPM [true, false] */
      enableBundling: true,
    
      /* Module bundle config for JSPM */
      bundles: {
    
        /* Name of bundle to exclude from all other bundles */
        defaultExclude: 'main-bundle',
    
        /* Build self-executing bundles [true, false] */
        selfExecuting: false,
    
        /* Array of module bundles config objects */
        items: [
          {
            /* Name of this bundle (optional) */
            name: 'main',
          
            /* Name of entry module for this bundle */
            entry: 'main',
    
            /* Array of bundles to exclude from this bundle */
            exclude: [],
    
            /* Array of polyfills for this bundle */
            polyfills: []
          },
          {
            /* Name of this bundle (optional) */
            name: 'home',
            
           /* Name of entry module for this bundle */
            entry: 'home',
    
            /* Array of bundles to exclude from this bundle */
            exclude: [],
    
            /* Array of polyfills for this bundle */
            polyfills: []
          }
        ]
      }
    },
    ...
}
js.processor

Type: String Default: jspm
Specifies the Javascript processor to be used. Possible values include jspm and none. A value of none will result in straight copy of the source Javascript files to the public and export directories. Note: no other Javascript configuration settings will apply if none is specified.

js.minify

Type: String Default: exportOnly
Specifies when Javascript files should be minified. Possible values include always, never, and exportOnly.

js.enableBundling

Type: Boolean Default: true
Specifies whether Javascript modules should be bundled during the development workflow. If set to false, Javascript modules will be copied directly to the public directory and loaded individually by the SystemJS module loader. This can facilitate quicker Javascript processing (as bundling can take several seconds) and easier debugging. This setting does not apply to the export workflow, during which bundling is always enabled.

js.bundles

Type: Object
Configuration settings for JSPM module bundling. See the Javascript Module Bundling section for more information.

js.bundles.defaultExclude

Type: string
The name of the bundle to exclude from all other bundles. This setting can be useful when you have a common bundle that loads on every page.

js.bundles.selfExecuting

Type: Boolean Default: false
Specifies whether or not to create self-executing bundles that are independent of the SystemJS module loader.

js.bundles.items

Type: Array
A list of module bundle configuration objects.

bundle.entry

Type: String
The name of the module that serves as the entry point for the bundle. The bundle will include this module and all of its dependencies.

bundle.name

Type: String Default: ``
The name of this bundle. This setting is optional and, if omitted, the bundle name will default to the name of the entry module.

bundle.exclude

Type: Array
A list of bundle names to exclude from this bundle. These should be "full" bundle names, meaning they should include the postfix -bundle

bundle.polyfills

Type: Array
A list of polyfills to apply to this bundle. Each item in this setting is a file path to a polyfill script relative to the Javascript directory. See the Polyfilled Bundles section for more information.

Listen Task Configuration

By default, the listen task will watch for asset file changes and run a build task when changes occur. This behavior is configurable:

listenTasks

Type: Array Default: '[build]'
The task(s) that the listen task will run when file changes occur. Possible values include build and export.

Javascript Module Bundling

Skeletor will generate module bundles for you based on the bundles configuration setting in your Gruntfile. Under the hood, Skeletor will iterate through your defined bundles and execute 'jspm bundle' commands. These bundles can either be standard SystemJS bundles or stand-alone, self-executing bundles (as specified in the bundles.selfExecuting setting).

Skeletor is configured to create SystemJS bundles by default. SystemJS bundles are not referenced in a web page directly, but rather by a modules within it:

<!-- Load the SystemJS script and its configuration file -->
<script src="../../js/jspm_packages/system.js"></script>
<script src="../../js/config.js"></script>
<script>
    <!-- Tell SystemJS to import the 'main' module, which will cause the 'main-bundle' bundle to be loaded -->
  System.import('main');
</script>

If you instead choose to use self-executing bundles independent of the SystemJS module loader, the method for loading a bundle is slightly different:

<!-- Load the bundleHelper file -->
<script src="../../js/bundleHelper.js"></script>
<script>
    <!-- Use bundleHelper to load the 'main-bundle' bundle -->
  bundleHelper.loadBundle('main-bundle');
</script>

Skeletor generates polyfill/bundle combinations and loads the correct combination based on client-side feature testing (see the Polyfilled Bundles section below). In order for Skeletor to accomplish this, self-executing bundles need to be loaded via bundleHelper, which is included in Skeletor.

Polyfilled Bundles

Overview

In many cases, your Javascript code will require certain feature polyfills in order to run correctly on legacy (or even modern) browsers. For delivering these polyfills to the client, Skeletor takes the conditional-build approach recommended by the yepnope.js team and others. At runtime, this approach includes the following steps:

  1. Run a series of feature tests on the client's browser
  2. Generate a bundle filename based on the results of the feature tests (i.e., append the names of the failed tests to the bundle's filename)
  3. Load the bundle from the server using this generated filename, which will include the appropriate feature polyfills

Generating Conditional Builds

At build time, Skeletor uses the bundle.polyfills setting in each bundle configuration to generate conditional builds of those bundles. Each conditional build is a combination of polyfills concatenated to the beginning of a bundle. For example, say you have a bundle called main-bundle that has two polyfills specified:

bundles: {
    ...,
    items: [
        {
            entry: 'main',
            polyfills: ['polyfills/classList', 'polyfills/assign']
        }   
    ]
}

Skeletor will generate four versions of the main-bundle bundle, one for each potential combination of polyfills needed by a client browser:

  • main-bundle.js (bundle without any polyfills)
  • main-bundle-classList.js (bundle with the classList polyfill)
  • main-bundle-assign.js (bundle with the assign polyfills)
  • main-bundle-classList-assign.js (bundle with the classList and assign polyfills)

Loading a Conditional Build

Skeletor contains a bundleHelper.js script that is responsible for performing feature tests on the client browser and then loading the correct polyfill/bundle combination based on the results of those tests. Inside bundleHelper.js, there are a handful of common feature test definitions already present:

var bundleHelper = function() {
  /* Add your polyfill test definitions here */
  var testDefs = {
    assign: Object.assign,
    fetch: self.fetch,
    find: Array.prototype.find
  }
  ...
}

A feature test definition is usually a concise one-liner. The name of the test should match the filename of the associated polyfill script, which lives in the js/polyfills/ directory.

The bundleHelper script also contains an empty array variable called bundles:

var bundleHelper = function() {
  ...
  var bundles = [],
  ...
}

At build time, this bundles array gets populated with bundle objects, one for each bundle defined in the js.bundles.items configuration setting in the Gruntfile. These bundle objects contain information about the feature tests needed for each bundle. These feature tests are references to the testDefs object.

At runtime, the bundleHelper script will iterate through each bundle object and run the specified feature tests on the client's browser. The script will then generate a bundle filename based on the failed feature tests. The final step is to intercept the loading of the bundle by the client browser and instead load the conditional build of the bundle.

  • If you are using SystemJS bundles, the SystemJS map configuration option is used to tell the module loader to load the conditional build of the bundle rather than the default build
  • If you are using self-executing bundles, SystemJS's map option isn't available. Instead, the bundleHelper script provides a loadBundle method that accepts a bundle name and loads the conditional build of the bundle.

The bundleHelper script is automatically concatenated to the SystemJS config.js script. If you are using self-executing bundles, you will instead need to load the bundleHelper.js script directly and then load bundles using the bundleHelper.loadBundle method.

Adding Polyfills

Adding a new polyfill to your project is a three-step process:

  1. Add the polyfill script to the js/polyfills/ directory
  2. Add the polyfill test to the testDefs object in the bundleHelper.js script. Make sure the test name matches the filename (minus the .js extension) of your polyfill script
  3. Add a reference to the polyfill in the appropriate bundle.polyfills setting in the Gruntfile

About

A Pattern Lab-friendly front-end web project boilerplate and build tool


Languages

Language:JavaScript 85.6%Language:PHP 7.4%Language:HTML 4.3%Language:CSS 2.5%Language:LiveScript 0.1%Language:Shell 0.0%