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
- Installation Instructions
- Atomic Design & Pattern Lab
- Themes
- Directory Structure
- Workflows
- Configuration
- Javascript Module Bundling
- Clone the Skeletor git repository into your project's root directory or a subdirectory
- From the command line, type
npm install
to install all Node dependencies - From the command line, type
grunt
to generate your first Pattern Lab build
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.
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.
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
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.
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.
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.
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
Skeletor supports two primary workflows: project development and exporting/CMS integration. Both workflows are controlled via the command line by invoking Grunt tasks.
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
.
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
.
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
.
Skeletor is built to be highly-configurable. The majority, if not all, of the configuration settings exist in the Gruntfile.
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',
...
}
};
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
}
}
}
A theme configuration object contains settings for a particular theme.
theme1: {
basePath: 'theme1',
parentTheme: 'baseTheme',
...
}
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.
Type: String
The name of the theme's parent theme. Setting a parent theme allows for CSS inheritance.
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'
}
},
...
}
Type: String
Default: [assetDirectory]
The base file path of the asset directory relative to the theme's directory.
Type: Object
The file paths of asset types (CSS, Javascript, images, etc) relative to the asset directory.
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.
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
},
...
}
Type: Boolean
Default: false
In addition to the processed CSS files, source CSS files are copied to the export directory during an export.
Type: Boolean
Default: true
CSS @import statements are generated automatically using globbing.
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: []
}
]
}
},
...
}
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.
Type: String
Default: exportOnly
Specifies when Javascript files should be minified. Possible values include always
, never
, and exportOnly
.
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.
Type: Object
Configuration settings for JSPM module bundling. See the Javascript Module Bundling section for more information.
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.
Type: Boolean
Default: false
Specifies whether or not to create self-executing bundles that are independent of the SystemJS module loader.
Type: Array
A list of module bundle configuration objects.
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.
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.
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
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.
By default, the listen
task will watch for asset file changes and run a build
task when changes occur. This behavior is configurable:
Type: Array
Default: '[build]'
The task(s) that the listen
task will run when file changes occur. Possible values include build
and export
.
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.
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:
- Run a series of feature tests on the client's browser
- 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)
- Load the bundle from the server using this generated filename, which will include the appropriate feature polyfills
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 theclassList
polyfill)main-bundle-assign.js
(bundle with theassign
polyfills)main-bundle-classList-assign.js
(bundle with theclassList
andassign
polyfills)
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 a new polyfill to your project is a three-step process:
- Add the polyfill script to the
js/polyfills/
directory - Add the polyfill test to the
testDefs
object in thebundleHelper.js
script. Make sure the test name matches the filename (minus the.js
extension) of your polyfill script - Add a reference to the polyfill in the appropriate
bundle.polyfills
setting in the Gruntfile