Generic scaffolding system, language agnostic
Generators (plugins) do the actual work & decision
Features
Scaffolding
Creating a new section of a project
Install globally "yo" and "generator-xxxxx"
Use:
yo something
--help
yo --generators : list
yo doctor : diagnose
yo something:sub
npm home: go to home page
Use generator for generator: https://github.com/yeoman/generator-generator
is a npm module
standard dir structure:
generator-yourname
package.json (see https://yeoman.io/authoring/) require keywords, dependency, files (to include all generators)
generators (optional, can also just put under root)
app (the default generator)
index.js (the generator, see https://yeoman.io/authoring/)
some-sub-generator
var Generator = require('yeoman-generator');
module.exports = class extends Generator {};
module.exports = class extends Generator {
constructor(args, opts) {
// Calling the super constructor is important so our generator is correctly set up
super(args, opts);
// Next, add your custom code
this.option('babel'); // This method adds support for a `--babel` flag
}
};
Every method is run once generator is called in sequence. Exceptions...
module.exports = class extends Generator {
method1() {
this.log('method 1 just ran');
}
};
Each task (method) will be run, excepts:
helper methods:
_start_with_underscore
instance methods in constructor `this.helperMethod = function() {...`
write a parent generator with helpers, then write generator
Run loop (for composing generators together):
run priorities: all tasks of same priority in all composed generators will be run together
1. initializing - Your initialization methods (checking current project state, getting configs, etc)
2. prompting - Where you prompt users for options (where you’d call this.prompt())
3. configuring - Saving configurations and configure the project (creating .editorconfig files and other metadata files)
4. default - If the method name doesn’t match a priority, it will be pushed to this group.
5. writing - Where you write the generator specific files (routes, controllers, etc)
6. conflicts - Where conflicts are handled (used internally)
7. install - Where installations are run (npm, bower)
8. end - Called last, cleanup, say good bye, etc
to define in code:
class extends Generator {
priorityName() {}
}
// or, for multiple tasks
Generator.extend({
priorityName: {
method() {},
method2() {}
}
});
Asynchronous tasks:
return a promise, loop will first resolve the promise
[not recommended] this.async()
https://yeoman.io/authoring/user-interactions.html
Always uses "this" methods, do not use "console.log()" etc
uses Inquirer.js:
attach answer in "this" for later usage
`store: true` to save user answer as default
module.exports = class extends Generator {
async prompting() {
this.answers = await this.prompt([
.............
]);
}
};
use "this.argument()" in constructor
this.option() method in constructor
this.log()
this.diff(actual, expected)
this.prompt(questions, callback)
Ways generators can be composed:
A generator composes with B generator
end user initiated (not yet available)
// compose with a path and options, 'require.resolve()' solves path for module
// to pass arguments, pass an Array as options.arguments
this.composeWith(require.resolve('generator-bootstrap/generators/app'), {preprocessor: 'sass'});
// [NOT encouraged, use generator namespace
this.composeWith('backbone:route', {rjs: true});
// Impose with generator class
// Import generator-node's main generator
const NodeGenerator = require('generator-node/generators/app/index.js');
// Compose with it
this.composeWith({
Generator: NodeGenerator,
path: require.resolve('generator-node/generators/app')
});
// run npm install, only once, no matter how many times called
this.npmInstall(['lodash'], { 'save-dev': true });
Extend or create package.json
writing() {
const pkgJson = {
devDependencies: {
eslint: '^3.15.0'
},
dependencies: {
react: '^16.2.0'
}
};
// Extend or create package.json file in destination path
this.fs.extendJSON(this.destinationPath('package.json'), pkgJson);
}
Yarn, Bower etc. see document
CLI:
install() {
this.spawnCommand('composer', ['install']);
}
this.destinationRoot() - destination (the current working dir or the one with .yo-rc.json file)
this.destinationPath('sub/path') - with joining path
this.contextRoot - where user run the command 'yo'
this.sourceRoot() - the "./templates" folder
this.templatePath('app/index.js') - joining
this.fs - :
https://github.com/sboudrias/mem-fs-editor
commit - DO NOT CALL, Yeoman will call it after conflict check
read - read file as string
readJSON
write
writeJSON
append
extendJSON - extend JSON file with partial objects
delete
copy - copy file, or copy dir (with 'from' being a glob pattern)
copyTpl - copy and rendering using ejs template (<%= // some javascript here %>)
move - file or glob pattern (internally it copy and delete)
exists
Every file of any type will be passed through this stream. Use gulp tools.
var beautify = require("gulp-beautify");
this.registerTransformStream(beautify({ indent_size: 2 }));
this.config.save() :
writes to .yo-rc.json
should always do this in :app generator (even nothing to write)
this.config.set(): set key with value, also calls save() automatically
this.config.get()
this.config.getAll()
this.config.delete()
this.config.defaults() - accept some defaults
.yo-rc.json file:
is name spaced, so generator configurations will not conflict
https://yeoman.io/authoring/testing.html
# to define a scope and enable debug mode
DEBUG=yeoman:generator
# to debug
node --inspect `which yo` <generator> [arguments]
Project root:
".yo-rc.json" marks the project root and is created from "this.config.save()" by generators. Yeoman searches this file for project root.
yeoman-environment is the core that other tools can integrate
API Documen: https://yeoman.github.io/generator/