The easiest build tool you'll ever use.
GitHub npmcd <your-project-name>
npm install millwright-cli -g
npm install millwright --save-dev
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
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.
Setting up your project to use Millwright is simple - here's how:
src
directory at the project root.
html
to
mustache
).
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
.
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
.
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
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.
└─ 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:
├─ 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.
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 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.
mill make
)├─ src | ├─ index.mustache | └─ index.json └─ dest └─ index.html
{ "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" ] }
<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>
<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>
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.
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
<p>Content from src/index.mustache</p>
<p>Content from src/extra.mustache</p>
<h1>Header from src/wrapper.mustache</h1> {[{> page}]} <p>Footer from src/wrapper.mustache</p>
<p>Content from src/nested-with-wrapper/index.mustache</p>
<h1>Header from src/nested-with-wrapper/wrapper.mustache</h1> {[{> page}]} <p>Footer from src/nested-with-wrapper/wrapper.mustache</p>
<p>Content from src/nested-without-wrapper/index.mustache</p>
<h1>Header from src/wrapper.mustache</h1> <p>Content from src/index.mustache</p> <p>Footer from src/wrapper.mustache</p>
<h1>Header from src/wrapper.mustache</h1> <p>Content from src/extra.mustache</p> <p>Footer from src/wrapper.mustache</p>
<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>
<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 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
.
mill make
)├─ src | ├─ index.mustache | ├─ nested | | └─ index.mustache | └─ partials | └─ content.mustache └─ dest ├─ index.html └─ nested └─ index.html
<p>Content from src/index.mustache</p> {[{> content}]}
<p>Content from src/nested/index.mustache</p> {[{> content}]}
<p>Content from src/partials/content.mustache</p>
<p>Content from src/index.mustache</p> <p>Content from src/partials/content.mustache</p>
<p>Content from src/nested/index.mustache</p> <p>Content from src/partials/content.mustache</p>
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.
mill make
)├─ src | ├─ index.mustache | └─ lambdas | └─ escape.mustache └─ dest └─ index.html
<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>
const escape = require('escape-html'); module.exports = {module: module, lambda: lambda}; function lambda(template) { return escape(template).trim(); }
<p> This paragraph is meant to be displayed as code, with tags included, so it has to be html escaped. </p>
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.
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.
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
<p>Content from src/index.mustache</p>
<p>Content from src/extra.mustache</p>
{ "assets": { "styles": [ "../node_modules/module-with-styles/styles.css" ] } }
{[{#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}]}
{ "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" ] } }
<p>Content from src/nested-with-wrapper/index.mustache</p>
{[{#assets.random-name}]} <link href="{[{.}]}" rel="stylesheet"> {[{/assets.random-name}]} {[{> page}]}
{ "assets": { "random-name": [ "more-styles.css" ] } }
<p>Content from src/nested-without-wrapper/index.mustache</p>
<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>
<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>
<link href="awesome-styles.css" rel="stylesheet"> <p>Content from src/nested-with-wrapper/index.mustache</p>
<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>
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.
└─ src ├─ index.mustache ├─ index.json ├─ some-es6.js ├─ coffeescript.coffee ├─ styles-in-cssnext.css ├─ sass.scss ├─ imports-work-too.scss ├─ less.less └─ stylus.styl
{[{#assets.styles}]} <link href="{[{.}]}" rel="stylesheet"> {[{/assets.styles}]} {[{#assets.scripts}]} <script src="{[{.}]}"></script> {[{/assets.scripts}]}
{ "assets": { "scripts": [ "some-es6.js", "coffeescript.coffee" ], "styles": [ "styles-in-cssnext.css", "sass.scss", "less.less", "stylus.styl" ] } }
Assets that are fetched via URL can be used by providing the complete url (including protocol) to a template data file.
└─ src ├─ index.mustache └─ index.json
{[{#assets.scripts}]} <script src="{[{.}]}"></script> {[{/assets.scripts}]}
{ "assets": { "scripts": [ "https://unpkg.com/lodash@4.17.4" ] } }
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.
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.
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.
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.
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.
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 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.
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
<p>Content from src/index.mustache</p>
<p>Content from src/extra.mustache</p>
{ "assets": { "styles": [ "../node_modules/module-with-styles/styles.css" ], "scripts": [ "random-directory/do-things.js" ] } }
{[{#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}]}
{ "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" ] } }
<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>
<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>
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 |
|
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. |
├─ src └─ millwright.json
{ "defaultCommand": "dev", "srcDir": "src", "destDir": "dest", "partialsDir": "partials", "lambdasDir": "lambdas", "assetIgnoredBasePaths: [ "src", "bower_components", "node_modules" ], templateTags: [ "{[{", "}]}" ] }
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
.
├─ src └─ package.json
{ ... "scripts": { "build": "mill build && grunt build" } }