millwright.js is currently in alpha. It's expected to work as described (this very site is built by Millwright on continuous deployment), but it's in need of testing under various use cases. If you encounter a bug, new issues on GitHub are encouraged and appreciated!

Calling all developers: Millwright is young, and there's lots of work to be done. If you like where the project is headed and want to help make it better, join us on GitHub and get in on the action! And be sure to star us, too!

millwright.js

The easiest build tool you'll ever use.

GitHub npm

cd <your-project-name>

npm install millwright-cli -g

npm install millwright --save-dev

Start projects faster.

A lot of web projects need the same things. Millwright does those things automatically. Template processing, asset transpiling, sourcemaps, local serving with file watching, and production builds, all with one command and no config. You can head straight for the docs, or just try it out on your machine.

Try it now

Try It Out

The fastest possible way to understand Millwright is to play with some code. Just go install it in a new directory, and then run the mill demo command to generate a small example project.

Project Setup

Setting up your project to use Millwright is simple - here's how:

  • Keep your project source code in a src directory at the project root.
  • Use mustache templates instead of html (change the extension from html to mustache).
  • Structure your src directory the way that you want your site structured. For example, if a page should be found at my-project.com/a/b/c.html, then the template for that page should be in src/a/b/c.mustache.
  • Use a wrapper if you have more than one page sharing a header and/or footer.
  • Declare your js and css assets.

Installation

Note: This guide assumes that you know how to install and save npm packages to your project. If that's not you, we recommend checking out their docs to get going.

Millwright requires two packages - a global package, which allows you to use the mill command, and a local package, which is installed per-project.

To install the global package, run npm install millwright-cli -g. Then, from inside your project directory, install the local package by running npm install millwright --save-dev.

Commands

Millwright currently utilizes a handful of commands centered around static site generation.

Note: these commands must be used from the root of your project, in the same directory as your package.json.

mill - alias for mill dev

mill make - generates a development build with no optimization

mill build - generates an optimized build for production use

mill dev - same as mill make, but serves locally and watches for changes.

mill preview - same as mill build, but serves locally. Does not watch for changes.

mill demo - generate an example project

Usage

Because of Millwright's zero configuration approach, usage documentation is less concerned with how to setup and run the tool, and is instead focused on how your project can take advantage of what Millwright does automatically.

Millwright requires a src directory (name can be changed via options). By default, everything in the src directory is simply copied to the dest directory (except for templates, which Millwright will always attempt to process). This means that simple sites or apps that don't use a build tool can begin using Millwright incrementally.

The following sections explain how to structure your project to take full advantage of Millwright.

file structure (before)

└─ src
    ├─ index.html
    ├─ about.html
    ├─ styles.css
    └─ more-pages
        ├─ index.html
        ├─ another-page.html
        ├─ scripts.js
        └─ styles.css

Given the file structure above, running mill make would produce this:

file structure (after)

├─ src
|   ├─ about.html
|   ├─ index.html
|   ├─ styles.css
|   └─ more-pages
|       ├─ index.html
|       ├─ another-page.html
|       ├─ scripts.js
|       └─ styles.css
└─ dest
    ├─ about.html
    ├─ index.html
    ├─ styles.css
    └─ more-pages
        ├─ index.html
        ├─ another-page.html
        ├─ scripts.js
        └─ styles.css

The files haven't been changed at all, just copied to a new dest directory. This directory would then serve as the web root for the project. In subsequent sections we'll look at the different things Millwright can do for static sites and apps.

Note: any supported template files (currently just mustache) will always be compiled to html when running a mill command.

Templates

Templates are html files with superpowers. When Millwright compiles a template, the output will be an html file. Millwright currently supports mustache templates, with other options coming soon.

Tags

Templating languages utilize special tags, which the templating library looks for when processing a file. Mustache uses double curly braces {{ }} as tags by default, but this tends to conflict with commonly used front end frameworks like Angular. For this reason, the default template tags for Millwright include a bracket in between the double curlies {[{ }]}, effectively sidestepping this issue.

The default tags can be changed if desired - check out options for more info.

Variables

Variables are the references provided by template tags, which are parsed during compilation. When a template variable is encountered, Millwright will look for a the associated value in the json file with the same name as the template. Variables found in src/example.mustache would be checked against src/example.json, if it exists. If the value is unavailable for some reason, Millwright will fall back to the json file associated with whatever wrapper is being used by the template. If no value is found, the tag will simply be removed from the template.

file structure (after running mill make)

├─ src
|   ├─ index.mustache
|   └─ index.json
└─ dest
    └─ index.html

src/index.json

{
  "page-title": "Example Website",
  "example-content": "This is example content from src/index.json."
  "some-list-items": [
    {"item": "Item 1"},
    {"item": "Item 2"},
    {"item": "Item 3"}
  ],
  "more-list-items": [
    "Item 4",
    "Item 5",
    "Item 6"
  ]
}

src/index.mustache

<head>
  <title>{[{page-title}]}</title>
</head>
<body>
  <p>{[{example-content}]}</p>

  <ul>
    {[{#some-list-items}]}
      <li>{[{item}]}</li>
    {[{/some-list-items}]}
  </ul>

  <ol>
    {[{#more-list-items}]}
      <li>{[{.}]}</li>
    {[{/more-list-items}]}
  </ol>
</body>

dest/index.html

<head>
  <title>Example Website</title>
</head>
<body>
  <p>This is example content from src/index.json.</p>

  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>

  <ol>
    <li>Item 4</li>
    <li>Item 5</li>
    <li>Item 6</li>
  </ol>
</body>

Wrappers

Most sites and apps have a common header and footer that are used sitewide. Millwright uses a "wrapper" concept to allow this kind of global markup to be kept in a single file. If a wrapper template, eg. wrapper.mustache is found in the src directory, all templates in that directory (including nested directories) will be injected into the wrapper template as a partial, using the reserved page variable.

Wrappers may also be nested. A template will use the nearest wrapper, starting from it's own directory, and then checking each parent directory until src.

Finally, note that wrappers are entirely optional. If no wrapper is found for a template, the template will be compiled as is.

file structure (after running mill make)

├─ src
|   ├─ index.mustache
|   ├─ extra.mustache
|   ├─ wrapper.mustache
|   ├─ nested-with-wrapper
|   |   ├─ index.mustache
|   |   └─ wrapper.mustache
|   └─ nested-without-wrapper
|       └─ index.mustache
└─ dest
    ├─ index.html
    ├─ extra.html
    ├─ nested-with-wrapper
    |   └─ index.html
    └─ nested-without-wrapper
        └─ index.html

src/index.mustache

<p>Content from src/index.mustache</p>

src/extra.mustache

<p>Content from src/extra.mustache</p>

src/wrapper.mustache

<h1>Header from src/wrapper.mustache</h1>

{[{> page}]}

<p>Footer from src/wrapper.mustache</p>

src/nested-with-wrapper/index.mustache

<p>Content from src/nested-with-wrapper/index.mustache</p>

src/nested-with-wrapper/wrapper.mustache

<h1>Header from src/nested-with-wrapper/wrapper.mustache</h1>

{[{> page}]}

<p>Footer from src/nested-with-wrapper/wrapper.mustache</p>

src/nested-without-wrapper/index.mustache

<p>Content from src/nested-without-wrapper/index.mustache</p>

dest/index.html

<h1>Header from src/wrapper.mustache</h1>

<p>Content from src/index.mustache</p>

<p>Footer from src/wrapper.mustache</p>

dest/extra.html

<h1>Header from src/wrapper.mustache</h1>

<p>Content from src/extra.mustache</p>

<p>Footer from src/wrapper.mustache</p>

dest/nested-with-wrapper/index.html

<h1>Header from src/nested-with-wrapper/wrapper.mustache</h1>

<p>Content from src/nested-with-wrapper/index.mustache</p>

<p>Footer from src/nested-with-wrapper/wrapper.mustache</p>

dest/nested-without-wrapper/index.html

<h1>Header from src/wrapper.mustache</h1>

<p>Content from src/nested-without-wrapper/index.mustache</p>

<p>Footer from src/wrapper.mustache</p>

Partials

Partials are templates that can be included within other templates. In mustache, partial tags are denoted by the greater-than symbol, eg. {[{> partial-name}]}. Millwright will look for partials in the src/partials directory, using the filename sans extension. For example, {[{> my-partial}]} will yield the contents of src/partials/my-partial.mustache.

file structure (after running mill make)

├─ src
|   ├─ index.mustache
|   ├─ nested
|   |   └─ index.mustache
|   └─ partials
|       └─ content.mustache
└─ dest
    ├─ index.html
    └─ nested
        └─ index.html

src/index.mustache

<p>Content from src/index.mustache</p>

{[{> content}]}

src/nested/index.mustache

<p>Content from src/nested/index.mustache</p>

{[{> content}]}

src/partials/content.mustache

<p>Content from src/partials/content.mustache</p>

dest/index.html

<p>Content from src/index.mustache</p>

<p>Content from src/partials/content.mustache</p>

dest/nested/index.html

<p>Content from src/nested/index.mustache</p>

<p>Content from src/partials/content.mustache</p>

Lambdas

Lambdas are JavaScript functions that you can write and use to implement custom processing for parts of your template. Some templating languages call their version of this "helpers". Millwright looks for lambdas in the src/lambdas directory. Unlike partials, lambdas don't have a special tag in mustache (instead the list tag {[{#...}]}{[{/...}]} is used), so the lambda file name must be prefixed with "lambdas", eg. {[{#lambdas.my-lambda}]}...{[{/lambdas.my-lambda}]}.

To allow maximum potential for this feature, each lambda is expected to be a node module. This means that lambdas can do anything that node modules can do, like requiring other modules that you've installed to your project.

Note that lambda node modules must export an object containing a reference to module itself, as well as the lambda function. This is because Millwright will use your module reference to require your lambda, so that modules required by your lambda are resolved correctly.

file structure (after running mill make)

├─ src
|   ├─ index.mustache
|   └─ lambdas
|       └─ escape.mustache
└─ dest
    └─ index.html

src/index.mustache

<pre>
{[{#lambdas.escape}]}
<p>
  This paragraph is meant to be displayed as code, with tags included, so it
  has to be html escaped.
</p>
{[{/lambdas.escape}]}
</pre>

src/lambdas/escape.js

const escape = require('escape-html');

module.exports = {module: module, lambda: lambda};

function lambda(template) {
  return escape(template).trim();
}

dest/index.html

&lt;p&gt;
  This paragraph is meant to be displayed as code, with tags included, so it
  has to be html escaped.
&lt;/p&gt;

Assets

Millwright's asset handling currently includes JavaScript and CSS files only.

Configuring build tools to process these files typically takes the most time when starting a new project - Millwright is meant to give this time back to the developer by only requiring paths to the assets. Everything else is handled without further input, including transpiling, sourcemapping, and optimization for production builds.

Including Assets

Millwright looks for asset paths in a project's json files, under the reserved assets property. This property's value should be an object composed of arrays of asset paths relative to the json file. As template data goes, these assets will be available to templates just as expected, except the path that is output for each asset will be absolute from site/app root in the dest directory.

It helps to think of the asset values provided in template data (json) files as simple variable values. The data structure in use is valid for mustache, and the values are output just like any other template data value. Millwright simply siphons information from these asset objects to determine what assets your project is using and which templates require them.

Lastly, because assets objects are just like any other template data value, the assets provided will be made available to templates just like data values. So assets provided to a wrapper template will be used in all templates that use that wrapper. Likewise, if assets are specified in the template data file of a specific template, eg. index.json, then those assets will be loaded especially for that file.

The same asset can be specified for as many templates as necessary, and Millwright will continue to improve how smart it is about reusing assets in production builds.

file structure (after running mill make)

├─ src
|   ├─ index.mustache
|   ├─ extra.mustache
|   ├─ extra.json
|   ├─ wrapper.mustache
|   ├─ wrapper.json
|   ├─ styles.css
|   ├─ random-directory
|   |   └─ do-things.js
|   ├─ nested-with-wrapper
|   |   ├─ index.mustache
|   |   ├─ wrapper.mustache
|   |   ├─ wrapper.json
|   |   └─ more-styles.css
|   └─ nested-without-wrapper
|       ├─ index.mustache
|       └─ index.json
└─ dest
    ├─ index.html
    ├─ extra.html
    ├─ styles.css
    ├─ random-directory
    |   └─ do-things.js
    ├─ nested-with-wrapper
    |   ├─ index.html
    |   └─ more-styles.css
    └─ nested-without-wrapper
        └─ index.html

src/index.mustache

<p>Content from src/index.mustache</p>

src/extra.mustache

<p>Content from src/extra.mustache</p>

src/extra.json

{
  "assets": {
    "styles": [
      "../node_modules/module-with-styles/styles.css"
    ]
  }
}

src/wrapper.mustache

{[{#assets.styles}]}
  <link href="{[{.}]}" rel="stylesheet">
{[{/assets.styles}]}

{[{#assets.scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.scripts}]}

{[{> page}]}

{[{#assets.some-more-scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.some-more-scripts}]}

src/wrapper.json

{
  "assets": {
    "styles": [
      "styles.css",
    ],
    "scripts": [
      "../node_modules/some-module/script.js"
    ],
    "some-more-scripts": [
      "../bower_components/some-package/another-script.js",
      "random-directory/do-things.js"
    ]
  }
}

src/nested-with-wrapper/index.mustache

<p>Content from src/nested-with-wrapper/index.mustache</p>

src/nested-with-wrapper/wrapper.mustache

{[{#assets.random-name}]}
  <link href="{[{.}]}" rel="stylesheet">
{[{/assets.random-name}]}

{[{> page}]}

src/nested-with-wrapper/wrapper.json

{
  "assets": {
    "random-name": [
      "more-styles.css"
    ]
  }
}

src/nested-without-wrapper/index.mustache

<p>Content from src/nested-without-wrapper/index.mustache</p>

dest/index.html

<link href="/styles.css" rel="stylesheet">

<script src="/some-module/script.js"></script>

<p>Content from src/index.mustache</p>

<script src="/some-package/another-script.js"></script>
<script src="/random-directory/do-things.js"></script>

dest/extra.html

<link href="/styles.css" rel="stylesheet">
<link href="/module-with-styles/styles.css" rel="stylesheet">

<script src="/some-module/script.js"></script>

<p>Content from src/extra.mustache</p>

<script src="/some-package/another-script.js"></script>
<script src="/random-directory/do-things.js"></script>

dest/nested-with-wrapper/index.html

<link href="awesome-styles.css" rel="stylesheet">

<p>Content from src/nested-with-wrapper/index.mustache</p>

dest/nested-without-wrapper/index.html

<link href="/styles.css" rel="stylesheet">

<script src="/some-module/script.js"></script>

<p>Content from src/nested-without-wrapper/index.mustache</p>

<script src="/some-package/another-script.js"></script>
<script src="/random-directory/do-things.js"></script>

Transpiling

Millwright can transpile code written in these JavaScript and CSS preprocessor languages:

Using preprocessor files works the same as with standard js/css files, just provide a path to the asset source in a template data file (see Including Assets for more info). Files can be named however you like, Millwright only reads the file extension to determine type.

Note that Millwright always runs plain JavaScript and CSS files through the ES2015 and CSSNext preprocessors, respectively. This may or may not be the best route, but results have been great so far. Your feedback is appreciated via Github issues.

file structure

└─ src
    ├─ index.mustache
    ├─ index.json
    ├─ some-es6.js
    ├─ coffeescript.coffee
    ├─ styles-in-cssnext.css
    ├─ sass.scss
    ├─ imports-work-too.scss
    ├─ less.less
    └─ stylus.styl

src/index.mustache

{[{#assets.styles}]}
  <link href="{[{.}]}" rel="stylesheet">
{[{/assets.styles}]}

{[{#assets.scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.scripts}]}

src/index.json

{
  "assets": {
    "scripts": [
      "some-es6.js",
      "coffeescript.coffee"
    ],
    "styles": [
      "styles-in-cssnext.css",
      "sass.scss",
      "less.less",
      "stylus.styl"
    ]
  }
}

URL Assets

Assets that are fetched via URL can be used by providing the complete url (including protocol) to a template data file.

file structure

└─ src
    ├─ index.mustache
    └─ index.json

src/index.mustache

{[{#assets.scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.scripts}]}

src/index.json

{
  "assets": {
    "scripts": [
      "https://unpkg.com/lodash@4.17.4"
    ]
  }
}

Sourcemaps

Sourcemaps allow you to interact with your original source files in browser development tools instead of the transpiled/minified/concatenated files that the browser is actually using.

Millwright places both sourcemaps and source files in the sourcemaps directory in your project's web root. You can set breakpoints in the JavaScript source files there, and style rule references will automatically map to source files as well.

Local Development

Millwright currently provides two kinds of commands: development and production. The development commands don't minify or concatenate your js/css, and provide file watching when the local server is running, along with automatically injecting changed code or reloading the page when appropriate.

Serving Locally

To serve files locally, you can run either the mill dev command, for a non-optimized build, ideal for development or the mill preview command, which produces and serves a production build, generally for testing purposes.

Millwright currently uses Browsersync for serving locally, but we're aware that some users may not be able to use it. Windows users, specifically, could run into issues. We want to address this potential issue based on developer feedback, so please open or comment on GitHub issues if serving locally isn't working for you.

Watching Files

Millwright will watch everything in a project's src directory for changes. In addition, files outside of source that are referenced as assets, such as node modules and bower components, will be watched as individual files. When a file changes, Millwright will re-run whatever portion of the build is necessary for the specific file or files that changed.

After a watched file changes and the partial build process runs, any connected browsers will be updated. If the changed file is a CSS or CSS preprocessor file, the any related CSS files will be rebuilt and injected to the page, without requiring a reload. For any other changes, the page will be reloaded.

Production Builds

Millwright provides production builds using the mill build command. Currently, that includes minifying and concatenating assets, but Millwright can do much more in the future with little or no changes to how you use it. Things like bundling and tree shaking are on our radar.

Minification

JavaScript and CSS assets are minified individually when running mill build. If your file is already minified (ends with .min.js or .min.css, Millwright won't attempt to minify it. If a minified file is found in the same directory as your source file, which is often the case with node modules or bower components, Millwright will just use that instead.

When minifying assets, Millwright will also provide sourcemaps, and previous sourcemaps from any transpiling will be passed through as well. See Sourcemaps for more info.

Concatenation

Concatenation is an optimization step where multiple files are combined into one so that the browser makes less requests. For those who are able to use HTTP/2 for your project and don't want concatenation, an option is expected for that soon.

When concatenating files, the resulting file is named based on the key used in the relevant template data (json) file. For template data files that handle a specific page, the page name is also used to avoid file naming collisions. If a page uses a wrapper with assets, the wrapper assets and page specific assets will be provided in separate files, so that the single wrapper script can be reused by all consumers.

file structure (after running mill build)

├─ src
|   ├─ index.mustache
|   ├─ extra.mustache
|   ├─ extra.json
|   ├─ wrapper.mustache
|   ├─ wrapper.json
|   ├─ scripts.js
|   ├─ more-scripts.js
|   ├─ styles.css
|   ├─ more-styles.css
|   └─ random-directory
|       └─ do-things.js
└─ dest
    ├─ index.html
    ├─ extra.html
    ├─ scripts.min.js
    ├─ styles.min.css
    ├─ extra-styles.min.css
    └─ extra-scripts.min.js

src/index.mustache

<p>Content from src/index.mustache</p>

src/extra.mustache

<p>Content from src/extra.mustache</p>

src/extra.json

{
  "assets": {
    "styles": [
      "../node_modules/module-with-styles/styles.css"
    ],
    "scripts": [
      "random-directory/do-things.js"
    ]
  }
}

src/wrapper.mustache

{[{#assets.styles}]}
  <link href="{[{.}]}" rel="stylesheet">
{[{/assets.styles}]}

{[{#assets.scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.scripts}]}

{[{> page}]}

{[{#assets.some-more-scripts}]}
  <script src="{[{.}]}"></script>
{[{/assets.some-more-scripts}]}

src/wrapper.json

{
  "assets": {
    "styles": [
      "styles.css",
      "more-styles.css
    ],
    "scripts": [
      "../node_modules/some-module/script.js",
      "scripts.js",
      "more-scripts.js"
    ],
    "some-more-scripts": [
      "../bower_components/some-package/another-script.js"
    ]
  }
}

dest/index.html

<link href="/styles.min.css" rel="stylesheet">

<script src="/scripts.min.js"></script>

<p>Content from src/index.mustache</p>

<script src="/some-more-scripts.min.js"></script>

dest/extra.html

<link href="/styles.min.css" rel="stylesheet">
<link href="/extra-styles.min.css" rel="stylesheet">

<script src="/extra-scripts.min.js"></script>

<p>Content from src/extra.mustache</p>

<script src="/some-more-scripts.min.js"></script>

Options

Millwright is designed to work without any tool configuration whatsoever, but if the defaults don't work for some reason, options can be set via millwright.json. This is a completely optional file. It should be added to the root of your project (alongside package.json) when used.

Here are the available options:

Option Default Description
defaultCommand 'dev' Command used when running mill
srcDir 'src' Name of source directory
destDir 'dest' Name of output directory
partialsDir 'partials' Name of partials directory
lambdasDir 'lambdas' Name of lambdas directory
assetIgnoredBasePaths
[
  (srcDir),
  'bower_components',
  'node_modules'
]
Path segments to remove when creating dest paths for assets (eg. node_modules/lib/script.js -> dest/lib/script.js). Must be an array of strings. Setting this option will replace the entire default array.
templateTags ['{[{', '}]}'] Delimiter tags used in templates, must be an array of two strings, with the opening and closing tags in order.

file structure

├─ src
└─ millwright.json

millwright.json

{
  "defaultCommand": "dev",
  "srcDir": "src",
  "destDir": "dest",
  "partialsDir": "partials",
  "lambdasDir": "lambdas",
  "assetIgnoredBasePaths: [
    "src",
    "bower_components",
    "node_modules"
  ],
  templateTags: [
    "{[{",
    "}]}"
  ]
}

Using With Other Tools

When you need functionality that Millwright doesn't offer, we recommend using npm scripts to link your build commands together. In the simplified example below, we create an npm script that combines mill build and a project's configured grunt build into a single command npm run build.

file structure

├─ src
└─ package.json

package.json

{
  ...
  "scripts": {
    "build": "mill build && grunt build"
  }
}