Denim
A lightweight, npm-based template engine.
Install / Use
/learn @FormidableLabs/DenimREADME
[![Travis Status][trav_img]][trav_site] [![Coverage Status][cov_img]][cov_site]
Denim
A lightweight, npm-based template engine.
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->Installation
Install this package as a global dependency.
$ npm install -g denim
Although we generally disfavor global installs, this tool creates new projects from scratch, so you have to start somewhere...
Usage
denim can initialize any package that npm can
install, including npm, GitHub, file, etc.
Invocation:
$ denim [flags] <module>
Flags:
--help
--version
--prompts
Examples:
$ denim templates-module
$ denim templates-module@0.2.0
$ denim FormidableLabs/templates-module
$ denim FormidableLabs/templates-module#v0.2.0
$ denim git+ssh://git@github.com:FormidableLabs/templates-module.git
$ denim git+ssh://git@github.com:FormidableLabs/templates-module.git#v0.2.0
$ denim /FULL/PATH/TO/templates-module
Internally, denim utilizes npm pack
to download (but not install) a templates package from npm, GitHub, file, etc.
There is a slight performance penalty for things like local files which have to
be compressed and then expanded again, but we gain the very nice benefit of
allowing denim to install anything npm can in exactly the same
manner that npm does.
Installing from a Relative Path on the Local Filesystem
One exception to the "install like npm does" rule is installation from the
local filesystem. Internally, denim creates a temporary directory
to expand the download from npm pack and executes the process in that
directory, meaning that relative paths to a target modules are now incorrect.
Accordingly, if you want to simulate a relative path install, you can try something like:
# Mac / Linux
$ denim "${PWD}/../templates-module"
# Windows
$ denim "%cd%\..\templates-module"
Automating Prompts
To facilitate automation, notably testing a module by generating a project
with denim and running the project's tests as part of CI, there is a
special --prompts=JSON_OBJECT flag that skips the actual input prompts and
injects fields straight from a JSON object.
$ denim <module> \
--prompts'{"name":"bob","quest":"popcorn","destination":"my-project"}'
Note that all required fields must be provided in the JSON object, no defaults
are used, and the init process will fail if there are any missing fields.
Tip: You will need a destination value, which is added to all prompts.
Template Modules
Templates are created within a first class npm module. It could be your
projects shared utilities module or a standalone template bootstrap module.
The main point is creating something npm-installable that is lightweight for
bootstrapping your templated projects.
A denim project is controlled with:
denim.js: A control file for user prompts and data.templates/: A directory of templates to inflate during initialization. This directory can be configured with user prompts / data by setting the special_templatesDirvariable to something different than"templates".
For example, in templates-module, we have a control file and templates
as follows:
denim.js
templates/
.babelrc
.editorconfig
.travis.yml
CONTRIBUTING.md
demo/app.jsx
demo/index.html
LICENSE.txt
package.json
README.md
src/components/{{componentPath}}.jsx
src/index.js
test/client/main.js
test/client/spec/components/{{componentPath}}.spec.jsx
test/client/test.html
{{_gitignore}}
{{_npmignore}}
Templates Module Data
Packages provide data for template expansion via a denim.js file in the
root of the module. The structure of the file is:
module.exports = {
destination: // A special prompt for output destination directory.
prompts: // Questions and responses for the user
derived: // Other fields derived from the data provided by the user
};
Note that denim requires destination output directories to not exist
before writing for safety and initialization sanity.
Special Variables
There are several default data fields provided by denim that can be overridden
in denim.js configuration files. A brief list:
- Control
_templatesDir("templates"): The directory root of the templates to use during inflation._templatesFilter(a noop function): A function with the signature(filePath, isIncluded)wherefilePathis the resolved path to a file (relative to templates dir), andisIncludedis a boolean indicating whether or not denim would ordinarily include it (e.g., it is not excluded by the.gitignore). An overriding function should returntrueorfalsebased on custom logic and can optionally use theisIncludedparameter from denim's default logic.
- File naming helpers
_gitignore(".gitignore")_npmignore(".npmignore")_npmrc(".npmrc"):_eslintrc(".eslintrc")
Imports and Dependencies
The denim.js file is require-ed from a temporary extracted directory
containing the full module. However, an npm install is not run in the
module directory prior to starting the initialization process. This means
that you can require in:
- Files contained in the module itself.
- Any standard node libraries. (E.g.,
require("path"),require("fs")).
Unfortunately, you cannot require third party libraries or things that may
be found in <module>/node_modules/. (E.g., require("lodash")).
This is a good thing, because the common case is that you will need nearly
none of the dependencies in denim.js prompting that are used in the module
itself, so denim remains lightening quick by not needing to do any
npm install-ing.
User Prompts
User prompts and responses are ingested using [inquirer][]. The prompts field
of the denim.js object can either be an array or object of inquirer
[question objects][inq-questions]. For example:
module.exports = {
// Destination directory to write files to.
//
// This field is deep merged and added _last_ to the prompts so that module
// authors can add `default` values or override the default message. You
// could further override the `validate` function, but we suggest using the
// existing default as it checks the directory does not already exist (which
// is enforced later in code).
destination: {
default: function (data) {
// Use the early `name` prompt as the default value for our dest directory
return data.name;
}
},
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?"
}
]
};
denim 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);
}
}
Derived Data
Module authors may not wish to expose all data for user input. Thus,
denim supports a simple bespoke scheme for taking the existing user
data and adding derived fields.
The derived field of the denim.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());
}
}
Special Data and Scenarios
.npmignore, .gitignore
The Problem
Special files like .npmrc, .npmignore, and .gitignore in a templates/
directory are critical to the correct publishing / git lifecycle of a created
project. However, publishing templates/ to npm as part of publishing the
module and even initializing off of a local file path via npm pack does not
work well with the basic layout of:
templates/
.gitignore
.npmignore
.npmrc
The problem is that the .npmignore affects and filters out files that will
be available for template use in an u
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
