Initialize projects from builder archetypes.
Install this package as a global dependency.
$ npm install -g builder-init
Although we generally disfavor global installs, this tool creates new projects from scratch, so you have to start somewhere...
TODO: Add usage, documentation.
https://github.com/FormidableLabs/builder-init/issues/6
Archetypes provide data for template expansion via an init.js
file in the
root of the archetype. The structure of the file is:
module.exports = {
prompts: // Questions and responses for the user
derived: // Other fields derived from the data provided by the user
};
User prompts and responses are ingested using inquirer. The prompts
field
of the init.js
object can either be an array or object of inquirer
question objects. For example:
module.exports = {
prompts: [
{
name: "name",
message: "What is your name?",
validate: function (val) {
// Validate functions return `true` if valid.
// If invalid, return `false` or an error message.
return !!val.trim() || "Must enter a name!";
}
},
{
name: "quest",
message: "What is your quest?"
}
]
};
builder-init
provides a short-cut of placing the name
field as the key
value for a prompts
object instead of an array:
module.exports = {
prompts: {
name: {
message: "What is your name?",
validate: function (val) { return !!val.trim() || "Must enter a name!"; }
},
quest: {
message: "What is your quest?"
}
}
};
Note - Async: Inquirer has some nice features, one of which is enabling
functions like validate
to become async by using this.async()
. For
example:
name: {
message: "What is your name?",
validate: function (val) {
var done = this.async();
// Let's wait a second.
setTimeout(function () {
done(!!val.trim() || "Must enter a name!")
}, 1000);
}
}
Archetype authors may not wish to expose all data for user input. Thus,
builder-init
supports a simple bespoke scheme for taking the existing user
data and adding derived fields.
The derived
field of the init.js
object is an object of functions with
the signature:
derived: {
// - `data` All existing data from user prompts.
// - `callback` Callback of form `(error, derivedData)`
upperName: function (data, cb) {
// Uppercase the existing `name` data.
cb(null, data.name.toUpperCase());
}
}
Presently, all files in the init/
directory of an archetype are parsed as
templates. We will reconsider this over time if escaping the template syntax
becomes problematic.
builder-init
mostly just walks the init/
directory of an archetype looking
for any files with the following features:
- An empty / non-existent
init/
directory is allowed, although nothing will be written out. - If an
init/.gitignore
file is found, the files matched in the templates directory will be filtered to ignore any.gitignore
glob matches. This filtering is done at load time before file name template strings are expanded (in case that matters).
builder-init
uses Lodash templates, with the following customizations:
- ERB-style templates are the only supported format. The new ES-style template strings are disabled because the underlying processed code is likely to include JS code with ES templates.
- HTML escaping by default is disabled so that we can easily process
<
,>
, etc. symbols in JS.
The Lodash templates documentation can be found at: https://github.com/lodash/lodash/blob/master/lodash.js#L12302-L12365
And, here's a quick refresher:
Variables
var compiled = _.template("Hi <%= user %>!");
console.log(compiled({ user: "Bob" }));
// => "Hi Bob!"
var compiled = _.template(
"Hi <%= _.map(users, function (u) { return u.toUpperCase(); }).join(\", \") %>!");
console.log(compiled({ users: ["Bob", "Sally"] }));
// => Hi BOB, SALLY!
JavaScript Interpolation
var compiled = _.template(
"Hi <% _.each(users, function (u, i) { %>" +
"<%- i === 0 ? '' : ', ' %>" +
"<%- u.toUpperCase() %>" +
"<% }); %>!");
console.log(compiled({ users: ["Bob", "Sally"] }));
// => Hi BOB, SALLY!
In addition file content, builder-init
also interpolates and parses file
names using an alternate template parsing scheme, inspired by Mustache
templates. (The rationale for this is that ERB syntax is not file-system
compliant on all OSes).
So, if we have data: packageName: "whiz-bang-component"
and want to create
a file-system path:
src/components/whiz-bang-component.jsx
The source archetype should contain a full file path like:
init/src/components/{{packageName}}.jsx
builder-init
will validate the expanded file tokens to detect clashes with
other static file names provided by the generator.