English | 简体中文
In reading this section, you'll learn how to create and distribute your own template.
└── my-template
├── template ··················· Template source files directory (Required, Can be configured with other names)
│ ├── lib ···················· Any directory (Recurse all subdirectories)
│ │ ├── {name}.js ·········· Any file name with interpolate (Auto rename by answers)
│ │ └── logo.png ··········· Any file without interpolate (Auto skip binary file)
│ └── package.json ··········· Any file contents with interpolate (Auto render interpolate by answers)
├── index.js ··················· Entry point (Optional, Template configuration file)
├── package.json ··············· Package info (Optional)
└── README.md ·················· README (Optional)
We built a template to help users get started with their own template.
$ caz template my-template
Feel free to use it to bootstrap your own template once you understand the below concepts.
A template repo may have a configuration file for the template which can be either a index.js
or main
field defined in package.json
.
It must export an object:
module.exports = {
// your config...
}
Type: Template
The configuration file can contain the following fields:
The name of the template.
- Type:
string
module.exports = {
name: 'my-template'
}
The version of the template.
- Type:
string
module.exports = {
version: '0.1.0'
}
Template source files directory.
- Type:
string
- Default:
'template'
module.exports = {
source: 'template'
}
The metadata you can use in the template files.
- Type:
Record<string, unknown>
module.exports = {
metadata: {
bio: 'my template generated',
year: new Date().getFullYear()
}
}
Upon metadata definition, they can be used in template files as follows:
<%= bio %>
// => 'my template generated'
<%= year %>
// => 2022 (current year)
Interactive prompts, use prompts, please refer to prompts docs.
- Type:
PromptObject | PromptObject[]
- Default:
'{ name: 'name', type: 'text', message: 'Project name' }'
module.exports = {
prompts: [
{ name: 'name', type: 'text', message: 'Project name' },
{ name: 'version', type: 'text', message: 'Project version' },
{ name: 'sass', type: 'confirm', message: 'Use sass preprocessor?', initial: true }
]
}
The following keys automatically assign initial values (from other config or system info):
name
- destination path basename, fallback:path.basename(dest)
version
- npm init config, fallback:'0.1.0'
author
- npm or git name configemail
- npm or git email configurl
- npm or git url config
The following keys automatically assign default validater:
name
- by validate-npm-package-nameversion
- by semveremail
- by RegExp/[^\s]+@[^\s]+\.[^\s]+/
url
- by RegExp/https?:\/\/[^\s]*/
Upon prompts answers, they can be used in template files as follows:
<%= name %>
// => User input text
<%= version %>
// => User input text
<% if (sass) { %>
// use sass preprocessor
<% } %>
Filter files that you want to output.
- Type:
Record<string, (answers: Answers) => boolean>
module.exports = {
prompts: [
{ name: 'sass', type: 'confirm', message: 'Use sass preprocessor?', initial: true }
],
filters: {
'*/*.scss': answers => answers.sass,
'*/*.css': answers => !answers.sass
}
}
Custom template engine helpers.
- Type:
Record<string, any>
- Default:
{ _: require('lodash') }
module.exports = {
helpers: {
upper: input => input.toUpperCase()
}
}
Upon registration, they can be used in template files as follows:
<%= upper('zce') %>
// => 'ZCE'
// lodash is always
<%= _.camelCase('wow caz') %>
// => 'wowCaz'
Auto install dependencies after generation.
- Type:
false | 'npm' | 'yarn' | 'pnpm'
- Default: According generated files contains
package.json
module.exports = {
// run `yarn install` after files emit.
install: 'yarn'
}
Auto init git repository after generation.
- Type:
boolean
- Default: According generated files contains
.gitignore
module.exports = {
// run `git init && git add && git commit` after files emit.
init: true
}
Template setup hook, execute after template loaded & inquire completed.
- Type:
(ctx: Context) => Promise<void>
- Ref: Context
module.exports = {
setup: async ctx => {
// You can get the following data in context
const {
template,
project,
options,
dest,
src,
config,
answers // inquire answers
} = ctx
console.log('template setup', ctx)
}
}
Package manager choose.
module.exports = {
// ...
prompts: [
{
name: 'install',
type: 'confirm',
message: 'Install dependencies',
initial: true
},
{
name: 'pm',
type: prev => prev ? 'select' : null,
message: 'Package manager',
hint: ' ',
choices: [
{ title: 'npm', value: 'npm' },
{ title: 'yarn', value: 'yarn' }
]
}
],
setup: async ctx => {
// Execute install according to user's choice.
ctx.config.install = ctx.answers.install && ctx.answers.pm
}
}
Dynamic setting template files directory.
module.exports = {
// ...
prompts: [
{
name: 'features',
type: 'multiselect',
message: 'Project features',
instructions: false,
choices: [
{ title: 'TypeScript', value: 'typescript', selected: true }
// ....
]
}
],
setup: async ctx => {
// Dynamic setting template files directory.
ctx.config.source = ctx.answers.features.includes('typescript')
? 'template/typescript'
: 'template/javascript'
}
}
Other settings, use your creativity as much as possible...
Template prepare hook, execute after template files prepare, before rename & render.
- Type:
(ctx: Context) => Promise<void>
- Ref: Context
module.exports = {
prepare: async ctx => {
// You can get the following data in context
const {
template,
project,
options,
dest,
src,
config,
answers,
files // before rename & render
} = ctx
console.log('template prepare', ctx)
}
}
Add files to be generated dynamically.
module.exports = {
prepare: async ctx => {
ctx.files.push({
path: 'additional.txt',
contents: Buffer.from('<%= name %> additional contents')
})
}
}
Template emit hook, execute after all files emit to the destination.
- Type:
(ctx: Context) => Promise<void>
- Ref: Context
module.exports = {
// You can get the following data in context
emit: async ctx => {
const {
template,
project,
options,
dest,
src,
config,
answers,
files // after rename & render
} = ctx
console.log('template emit')
}
}
Generate completed callback. if got a string, print it to the console.
- Type:
string
or(ctx: Context) => string | Promise<void | string>
- Default: Log all generated files.
- Ref: Context
callback
module.exports = {
complete: async ctx => {
// ctx => all context
console.log(' Happy hacking ;)')
}
}
or string
module.exports = {
complete: ' Happy hacking ;)'
}
For more examples, please refer to the fixtures.
/**
* Creator context.
*/
interface Context {
/**
* Template name.
* e.g.
* - offlical short name: `nm`
* - offlical short name with branch: `nm#master`
* - custom full name: `zce/nm`
* - custom full name with branch: `zce/nm#master`
* - local directory path: `~/templates/nm`
* - full url: `https://github.com/zce/nm/archive/master.zip`
*/
readonly template: string
/**
* Project name, which is also the project directory.
*/
readonly project: string
/**
* More options.
*/
readonly options: Options & Record<string, any>
/**
* The source directory where the template (absolute).
*/
src: string
/**
* Generated result output destination directory (absolute).
*/
dest: string
/**
* Template config.
*/
readonly config: Template
/**
* Template prompts answers.
*/
readonly answers: Answers<string>
/**
* Template files.
*/
readonly files: File[]
}
/**
* Template config.
*/
export interface Template {
/**
* Template name.
*/
name: string
/**
* Template version.
*/
version?: string
/**
* Template source dirname.
*/
source?: string
/**
* Template metadata.
*/
metadata?: Record<string, unknown>
/**
* Template prompts.
*/
prompts?: PromptObject | PromptObject[]
/**
* Template file filters.
*/
filters?: Record<string, (answers: Answers<string>) => boolean>
/**
* Template engine helpers.
*/
helpers?: Record<string, unknown>
/**
* Auto install dependencies.
*/
install?: false | 'npm' | 'yarn' | 'pnpm'
/**
* Auto init git repository.
*/
init?: boolean
/**
* Template setup hook, execute after template loaded & inquire completed.
*/
setup?: (ctx: Context) => Promise<void>
/**
* Template prepare hook, execute after template files prepare, before rename & render.
*/
prepare?: (ctx: Context) => Promise<void>
/**
* Template emit hook, execute after all files emit to the destination.
*/
emit?: (ctx: Context) => Promise<void>
/**
* Template all completed.
*/
complete?: ((ctx: Context) => string | Promise<string> | Promise<void>) | string
}
/**
* File info.
*/
export interface File {
/**
* File full path
*/
path: string
/**
* File contents (buffer)
*/
contents: Buffer
}
Because the template will automatically install its production dependencies before it works, so you can normally use the third-party NPM module in the template configuration file.
e.g.
Install chalk
as production dependencies:
$ npm install chalk --save
index.js
:
const chalk = require('chalk')
NOTE: Only production dependencies are automatically installed.
Install caz
as devDependencies:
$ npm install caz --save-dev
Then in your template configuration file:
/** @type {import('caz').Template} */
module.exports = {
// Have type hint and IntelliSense (VSCode)
}
If you want direct output template interpolate, like this:
<%= '\<%= name %\>' %>
=><%= name %>
<%= '${name}' %>
=>${name}