:rocket: ngx-rocket/core
Core generator for creating ngX-Rocket add-ons
Table of Contents
Usage
This package extends Yeoman base generator with all the boilerplate needed to create
ngX-Rocket add-ons, and even more.
First install the dependency:
npm install --save @ngx-rocket/core
Then create a new add-on generator like this:
'use strict';
const Generator = require('@ngx-rocket/core');
module.exports = Generator.make({ baseDir: __dirname });
Add some template files in a templates/
folder and you're done.
Congratulations on making your first Yeoman generator! :tada:
Concepts
This package allows you to create advanced Yeoman generators that can be used as
ngX-Rocket add-ons or standalone generators.
See this example addon, you can use it as a base for your own add-ons.
First it may be helping to be familiar with Yeoman generators, since they have to
follow specific naming and structure rules.
Prompts and properties
You may want to ask the user some question about what he wants to generate, to do so you have to provide a list of
prompts like this:
module.exports = Generator.make({
baseDir: __dirname,
prompts: [
{
type: 'confirm',
name: 'sayHello',
message: 'Shall we say hello?',
default: true
},
{
type: 'input',
name: 'helloName',
message: 'To whom shall we say hello?',
default: 'world',
when: props => props.sayHello
}
]
});
You can see in this example that prompts can even be shown conditionally using the property when
.
After the user has answered your prompts, the results will be exposed as properties of the props
object.
The props
object can then be used directly in your templates.
To avoid repeating the same questions between core and add-ons generators, these props
can be shared and retrieved
using Generator.shareProps
and Generator.sharedProps
.
Note that in that goal, prompts name matching already defined properties with be automatically skipped once defined.
Templates
Your generator templates should go under the generators/app/templates/
folder.
With its most basic usage, any file put in this folder will be copied as-is in the generated project folder.
But you may often have to customize the file depending of the user prompts.
To do so, just prepend an underscore (_
) to the file name and it will become an EJS template, will
full access to the props
object:
// generators/app/templates/_hello.md
# <%= props.appName // From shared properties %>
<% if (props.sayHello) { -%>
Hello <%= props.helloName %>!
<% } else { -%>
...was not in the mood to say hello :-(
<% }-%>
You can then inject strings from the prompts or apply conditions for example.
See the EJS documentation for the complete syntax options.
File prefix rules
To avoid using complex hardcoded logic in generator, prefix-based file naming rules are used.
Conditional prefix
There is two ways to conditionally include a file depending of user choices:
-
Using a conditional folder at root level, with the syntax __<prefix>/
:
all files under this folder will be copied if the prefix condition is matched.
Example: __authentication/my-service.ts
will be copied to <project-dir>/my-service.ts
only if the user has
enabled authentication during prompts.
-
Using a conditional prefix on a specific file, with the syntax __<prefix>.<filename>
:
this file will be copied if the prefix condition is matched.
You can even complete this by adding _
before the <filename>
part to also make it an EJS template, ie
__<prefix>._<filename>
.
Example: __authentication.myservice.ts
will be copied to <project-dir>/my-service.ts
only if the user has
enabled authentication during prompts.
Multiple conditions are also supported using the +
character: __<prefix1>+<prefix2>+<prefixN>.<filename>
.
You can use the default prefix rules and extend them if needed.
These rules match the questions asked by the main generator
(generator-ngx-rocket):
web
: the user has chosen to make a web app as one of its targetscordova
: the user has chosen to make a mobile app as one of its targetselectron
: the user has chosen to make a desktop app as one of its targetspwa
: the user has chosen to add progressive web app supportbootstrap
: the user has chosen Bootstrap for its UIionic
: the user has chosen Ionic for its UImaterial
: the user has chosen Angular Material for its UIraw
: the user has chosen to not use any UI libraryuniversal
: the user has chosen to use Angular Universal (Server-Side Rendering)auth
: the user has enabled authenticationios
: the user has chosen to support iOS for its mobile appandroid
: the user has chosen to support Android for its mobile appwindows
: the user has chosen to support Windows (Universal) for its mobile app
Action prefix
In addition to conditional prefix, you can specify how the file should be copied to the destination folder.
By default files are copied entirely, overwriting previous version if needed (if multiple add-ons are trying to write
the same file, the one finally written will be from the last processed add-on).
To use actions you have to use this syntax (<action>).<filename>
. This syntax can also be combined with the
conditional/template prefixes like this: __<conditional-prefix>(<action>)._<filename>
Example: (merge).package.json
will merge the content with <project-dir>/package.json
.
This is the list of currently implemented file actions:
merge
(only for JSON files): performs a deep merge of the JSON properties, concatenating arrays with unique values.raw
: disable template processing even if filename starts with an underscore (_
).
Advanced customization
If your generator needs to perform additional specific actions, you can add code for custom tasks to be executed as
part of the composition lifecycle.
For this you simply create a new class to extend the base Generator
:
const Generator = require('@ngx-rocket/core');
const pkg = require('../../package.json');
class ExampleGenerator extends Generator {
initializing() {
this.version = pkg.version;
this.log(`Example generator is running version ${this.version}`);
}
end() {
this.log(`This was nice, see ya!`);
}
}
module.exports = Generator.make({
baseDir: __dirname,
generator: ExampleGenerator
});
A complete working example is available here:
addon-example
There is a set of specific task names for each part of the project generation lifecycle, for example the initializing
task of all generators will be executed before moving on to the next one.
To learn more about Yeoman's run loop and see the list of specific task with their priorities, see the
running context documentation. The composability docs also have
example execution sequence to understand how generators work
with each other.
See also the full Yeoman Base generator documentation for the list of
available properties and methods you can use in your generator.
Note: be careful when overriding one of the methods already defined in @ngx-rocket/core
base Generator (prompting
or writing
)! To maintain the base behavior along with your additions, you have to manually call the original method
using super.<method>()
in your overridden method, like this:
class ExampleGenerator extends Generator {
writing() {
console.log('Hey there!');
return super.writing();
}
}
Generating only tools
As part of the update process or if your users are only interested with generating only the toolchain, you can define
filters to exclude template files that are not part of the toolchain.
All generators automatically support the --tools
option that enable the filters.
To define these filters you have 2 possibilities:
-
Create a .toolsignore
in your generator baseDir
root to exclude files that are not part of the toolchain.
It uses the same syntax as a .gitignore
file.
-
Specify a toolsFilter
option when creating your generator instance. Note that
setting this option will remplace any rules that may be defined in a .toolsignore
file.
This option takes either a string or an array of strings, using the same syntax as a
.gitignore
file.
Standalone note
If you want your generator to work as a standalone and not only as an ngX-Rocket add-on, you must define the appName
propery, either using an argument option:
class MyStandaloneGenerator extends Generator {
initializing() {
this.argument('appName', {
desc: 'Name of the app to generate',
type: String,
required: true
});
}
}
or a prompt:
module.exports = Generator.make({
baseDir: __dirname,
prompts: [
{
type: 'input',
name: 'appName',
message: 'What\'s the name of your app?'
}
]
});
or both :)
Fullstack mode
By default, an add-on is configured to generate client templates, but by setting the type
option you can generate server templates or both client and
server templates.
When any add-on generator is either configured as server
or fullstack
, the whole project generation switches to
fullstack mode, meaning that the generated output will contain both client and server code.
At any time after the initialization of your generator you can check if fullstack mode is enabled by using the
isFullstack()
instance method.
The client and server output folders can be modified by the user or forced by your generator through the environment
variables NGX_CLIENT_PATH
and NGX_SERVER_PATH
. If not modified, client
and server
will be used as default
output folders.
API
Static methods/properties
Generator.make(options)
Creates a new Yeoman generator extending the core ngx-rocket generator.
{object}
options Configures your generator instance:
baseDir
: base directory for your generator templatesgenerator
: your generator base class (optional)options
: generator options, see related section at http://yeoman.io/authoring/user-interactions.html (optional).prompts
: generator prompts, using Inquirer.js format (optional).templatesDir
: generator templates directory (optional, default: 'templates'
)prefixRules
: generator template prefix rules (optional, default: Generator.defaultPrefixRules()
)toolsFilter
: file filter patterns to use when toolchain only option is enabled. If not provided, the generator
will try to load the .toolsignore
file inside baseDir
.type
: generator type, can be client
, server
or fullstack
(optional, default: 'client'). In fullstack
mode, client and server templates must be separated into client
, server
and root
subfolders.
Generator.defaultPrefixRules
Gets the default prefix rules.
The default rules are these:
{
web: props => props.target.includes('web'),
cordova: props => props.target.includes('cordova'),
electron: props => props.target.includes('electron'),
pwa: props => Boolean(props.pwa),
bootstrap: props => props.ui === 'bootstrap',
ionic: props => props.ui === 'ionic',
raw: props => props.ui === 'raw',
universal: props => Boolean(props.universal),
auth: props => Boolean(props.auth),
ios: props => props.mobile.includes('ios'),
android: props => props.mobile.includes('android'),
windows: props => props.mobile.includes('windows')
};
You can use this method to extend the default rules with your own, like this:
const extentedRules = Object.assign(Generator.defaultPrefixRules, {
hello: props => Boolean(props.hello)
});
Generator.sharedProps
Gets a copy of properties shared between generators.
To share additional properties, use Generator.shareProps
.
Also available on the generator instance.
Generator.shareProps(props)
Sets additional properties shared between generators.
To avoid collisions issues, only properties that are currently undefined will be added.
Also available on the generator instance.
Instance properties
sharedProps
(read-only)
See Generator.sharedProps
.
shareProps(props)
See Generator.shareProps
.
isStandalone
(read-only)
Returns true
if the generator is running standalone or false
if it is running as an add-on.
isFullstack
(read-only)
true
if this or a composed generator has declared to be in server
or fullstack
mode or false
if it is running
in client only mode.
packageManager
(read-only)
Returns the package manager to use (either npm
or yarn
).
The default value is npm
, and can be changed either by the --packageManager
option or the environment variable
NGX_PACKAGE_MANAGER
.