A truly modular yeoman generator for AngularJS all device apps.
This generator originated in the pure hate towards repetition.
It comes with a basic gulp setup that offers you some useful tasks for development (autoinjection of all development sass and javascript files, unit-tests, e2e tests and more), for building your app (minification of everything) and for building cordova multi-platform hybrid apps. It further adds a set of useful sub-generators (e.g.: the component generator yo moda:d myDirective
) that creates your development and testfiles in one simple sweep. The generated files follow John Papas angular styleguide.
I want this to be really good, so I would be excited to hear about your thoughts and what possible features you might be missing. Any other kind of help is also highly appreciated :)
I'm assuming that you have node already installed in a proper way.
Install bower and yeoman if not done already:
npm install -g bower yo
Install the generator:
npm install -g generator-moda
Then make a new directory and cd into it
mkdir my-super-modular-project && cd $_
Run it!
yo moda [app-name]
Then wait........ finally run:
gulp serve
for development.
- total injection: Basically everything you create is automatically injected where it needs to be and removed when its gone. Creating a new scss-file? No problem it's in your main.scss! Deleting an unused component? It's gone from your index.html.
- gulp and libsass speedified: It's amazing how much faster both are compared to their counterparts.
- super modular: No more controller madness by design.
- fully configurable and extendable - use your own configuration and your own templates
- cordova-prepared: Your cordova-build is just on step away. Building multiplatform mobile apps has never been so easy.
- and of course all the basics:
- livereload, sass-compilation, jshint (optional), jscs, and testing on runtime
- minification via
ng-annotate
,imagemin
,htmlmin
,cssmin
- unit-testing and end-to-end testing via karma runner
- pick your modules on creation (
ui.router
,ngMaterial
or one of the base components, you name it)
While offering the most common features of popular angularjs-generators there are three unique selling points:
The first one is the feature of auto-injection. As I said: I hate repition. And while other generators offer you also some kind of auto-injection they usually require you to use their generators for that, which also means you still need to manually change those when a file changes or is deleted. Not with moda
you not. This feature works for bower-components, your javascript-files, your tests and also your scss-components.
The second one is the configurability. It's super easy to adjust everything to your liking. Don't like the templates? No problem, use your own! Don't like the the file-names produced? Just make some simple changes in the .yo-rc.json. Best of all: The changes you make can be stored on a project level, so all developers in your team (can) use the same generator output.
The third one is the cordova integration. While technically not stunningly complicated to do, I know many developers who shy away before building hybrid apps, as they don't know how easy it is to do. And while I like ionic and while I'm thankful for what they did for hybrid-app development, I don't like that you that your kind of limted to mobile only and that you kind of have to use their components. The latter produces comparably fat apps and leaves you a little bit lost, if something doesn't work as expected. But it's just JavaScript and CSS run in a container wrapped by cordova after all, so using bootrap or your own components should work just as fine.
Finally it is safe to say that I'm really dedicated to this project. I'm an Angular and node developer for years know and I want this to be really good. I plan to spend lots of hours of my free time to improve it as the time goes on. So if you miss anything, don't like something chances are very good that I'm going to change that.
- What belongs together should be reflected in the file-structure. Grouping files by module is generally preferable to grouping files by type.
- Directives are the way to go. Build components all the way. They're sexy enclosed logic and expressive. Chances are you'll reuse them and it is no problem if it is only in your current app.
- Use controllers economically. They will be gone in Angular 2.0 and honestly - I'm not too sad about it. Use them on a page-level (if your app has something like that) to get data for your views or for very minor and very specific logic.
As per default the following tasks are available at your convenience:
gulp
: The development task. Runs all the injectors on file-change, file-creation or file-deletion. Unit-tests are run in parallel, as well as the sass-compilation.gulp injectAll
: Runs all the injectors once.gulp build
: Minifies your JavaScript via ng-annotate, your css, your images and your html files and copies everything to the www-folder.gulp test
: Runs your unit tests with the keep-alive option.gulp testSingle
: Runs your unit tests once.gulp e2e
: Runs your end to end tests once.
The mobile tasks require a little preparation described in the next section.
gulp cordovaDev
: Symlinks your app-folder to www and runs the emulator for easy live development.gulp cordovaRun
: Symlinks your app-folder to www and runs it on your device if connected.
Of course there are also all the standard cordova commands available as well. If you want to build a release run:
gulp build
cordova build android --release
For all cordova related commands there is an optional platform parameter you can use to specify the platform for the cordova task. E.g. gulp cordovaDev --platform=android
to run the android emulator. Alternatively you can edit the config.js to change the default platform.
All tasks can be edited freely and can be found in the /tasks folder.
- moda (aka moda:app)
- directive
- component
- service
- factory
- controller
- provider
- decorator
- route
- e2e-test
- page-object
For configuring the generators on a project-level edit the .yo-rc.json.
The main generator. Sets up the basic boilerplate. Provides an interactive prompt to install the most common modules.
Interactively generates a directive, a test file and if you choose so a scss and html-template files.
usage:
yo moda:d my-directive
output:
app/scripts/my-directive/my-directive-d.js
app/scripts/my-directive/my-directive-d.spec.js
// If you choose the template option (default:true)
app/scripts/my-directive/my-directive-d.html
app/scripts/my-directive/_my-directive-d.scss
// If you choose service for the service or factory option (default:false)
app/scripts/my-directive/my-directive-s.js
// If you choose factory
app/scripts/my-directive/my-directive-f.js
You can also specify a path or a parent module:
yo moda:d my-directive my-path
output:
app/scripts/my-path/my-directive/my-directive-d.js
app/scripts/my-path/my-directive/my-directive-d.spec.js
By default directives are wrapped into their own folder. If you don't want that you can edit the .yo-rc.json or specify the --noParentFolder
flag:
yo moda:d my-directive my-path --noParentFolder
Interactively generates a component, a test file and if you choose so a scss and html-template files.
usage:
yo moda:cp my-component
output:
app/scripts/my-component/my-component-cp.js
app/scripts/my-component/my-component-cp.spec.js
// If you choose the template option (default:true)
app/scripts/my-component/my-component-cp.html
app/scripts/my-component/_my-component-cp.scss
// If you choose service for the service or factory option (default:false)
app/scripts/my-component/my-component-s.js
// If you choose factory
app/scripts/my-component/my-component-f.js
You can also specify a path or a parent module:
yo moda:cp my-component my-path
output:
app/scripts/my-path/my-component/my-component-cp.js
app/scripts/my-path/my-component/my-component-cp.spec.js
By default components are wrapped into their own folder. If you don't want that you can edit the .yo-rc.json or specify the --noParentFolder
flag:
yo moda:cp my-component my-path --noParentFolder
Creates a controller and a test-file and template and service-files if you choose so. Although it works just fine, most of the time you probably would want to use route or component instead.
usage:
yo moda:c my-ctrl
output:
app/scripts/my-ctrl/my-ctrl-c.js
app/scripts/my-ctrl/my-ctrl-c.spec.js
// If you choose the template option (default:true)
app/scripts/my-ctrl/my-ctrl-c.html
app/scripts/my-ctrl/_my-ctrl-c.scss
// If you choose service for the service or factory option (default:false)
app/scripts/my-ctrl/my-ctrl-s.js
// If you choose factory
app/scripts/my-ctrl/my-ctrl-f.js
You can also specify a path or a parent module:
yo moda:c my-ctrl my-path
output:
app/scripts/my-path/my-ctrl/my-ctrl-d.js
app/scripts/my-path/my-ctrl/my-ctrl-d.spec.js
By default controllers are wrapped into their own folder. If you don't want that you can edit the .yo-rc.json or specify the --noParentFolder
flag:
yo moda:c my-ctrl my-path --noParentFolder
Creates a service.
usage:
yo moda:s my-service
output:
app/scripts/my-global-service-dir/my-service-s.js
app/scripts/my-global-service-dir/my-service-s.spec.js
Setting the default global-service (and factory) directory can be done by editing the .yo-rc.json
You can specify a path or a parent module:
yo moda:s my-service my-path
output:
app/scripts/my-path/my-service-s.js
app/scripts/my-path/my-service-s.spec.js
Creates a factory.
usage:
yo moda:f my-factory
output:
app/scripts/my-global-service-dir/my-factory-f.js
app/scripts/my-global-service-dir/my-factory-f.spec.js
Setting the default global-service (and factory) directory can be done by editing the .yo-rc.json
You can specify a path or a parent module:
yo moda:s my-factory my-path
output:
app/scripts/my-path/my-factory-f.js
app/scripts/my-path/my-factory-f.spec.js
Creates a filter.
usage:
yo moda:filter my-filter
output:
app/scripts/my-global-filter-dir/my-filter-filter.js
app/scripts/my-global-filter-dir/my-filter-filter.spec.js
Setting the default global-filter directory can be done by editing the .yo-rc.json
You can specify a path or a parent module:
yo moda:s my-filter my-path
output:
app/scripts/my-path/my-filter-filter.js
app/scripts/my-path/my-filter-filter.spec.js
Creates a provider.
usage:
yo moda:p my-provider
output:
app/scripts/my-global-service-dir/my-provider-p.js
app/scripts/my-global-service-dir/my-provider-p.spec.js
Setting the default global-service (and factory) directory can be done by editing the .yo-rc.json
You can specify a path or a parent module:
yo moda:p my-provider my-path
output:
app/scripts/my-path/my-provider-p.js
app/scripts/my-path/my-provider-p.spec.js
Creates a decorator.
usage:
yo moda:decorator my-decorator
output:
app/scripts/my-global-service-dir/my-decorator-decorator.js
app/scripts/my-global-service-dir/my-decorator-decorator.spec.js
Setting the default global-service directory can be done by editing the .yo-rc.json
You can specify a path or a parent module:
yo moda:decorator my-decorator my-path
output:
app/scripts/my-path/my-decorator-decorator.js
app/scripts/my-path/my-decorator-decorator.spec.js
Interactively creates a route (state) from a state-name. Requires ui.router
to be installed during app-creation.
usage:
yo moda:r my-state
output:
// if you choose to create a controller for the route (default:true)
app/scripts/_routes/my-state/my-state-c.js
app/scripts/_routes/my-state/my-state-c.spec.js
// If you choose the template option (default:true)
app/scripts/_routes/my-state/my-state-c.html
app/scripts/_routes/my-state/_my-state-c.scss
// If you choose service for the service or factory option (default:false)
app/scripts/_routes/my-state/my-state-s.js
app/scripts/_routes/my-state/my-state-s.spec.js
// If you choose factory
app/scripts/_routes/my-state/my-state-f.js
app/scripts/_routes/my-state/my-state-f.spec.js
Furthermore the state is injected into the routes.js
if present:
.state('my-state', {
url: '/my-state',
// if you choose the controller option
controller: 'MyStateCtrl',
// if you choose the template option
templateUrl: 'scripts/_routes/my-state/my-state-c.html'
})
/* STATES-NEEDLE - DO NOT REMOVE THIS */;
Setting the default routes directory can be done by editing the .yo-rc.json
You can also create sub-states like this:
yo moda:r my-par-state.my-state
output:
// if you choose to create a controller for the route (default:true)
app/scripts/_routes/my-par-state/my-state/my-state-c.js
app/scripts/_routes/my-par-state/my-state/my-state-c.spec.js
// If you choose the template option (default:true)
app/scripts/_routes/my-par-state/my-state/my-state-c.html
app/scripts/_routes/my-par-state/my-state/_my-state-c.scss
// If you choose service for the service or factory option (default:false)
app/scripts/_routes/my-par-state/my-state/my-state-s.js
app/scripts/_routes/my-par-state/my-state/my-state-s.spec.js
// If you choose factory
app/scripts/_routes/my-par-state/my-state/my-state-f.js
app/scripts/_routes/my-par-state/my-state/my-state-f.spec.js
Furthermore the state is injected into the routes.js
if present:
.state('my-par-state.my-state', {
url: '/my-state',
// if you choose the controller option
controller: 'MyStateCtrl',
// if you choose the template option
templateUrl: 'scripts/_routes/my-par-state/my-state/my-state-c.html'
})
/* STATES-NEEDLE - DO NOT REMOVE THIS */;
not implemented yet
not implemented yet
Parameters
--openInEditor
opens the created files automatically in your favorite editor (specified via .yo-rc.json)--useDefaults
skips the dialogs and uses the defaults--skipInject
skips running grunt inject after file creation--noParentFolder
does prevent the creation of a wrapper directory with the component name for directives and controllers
Chances are that the default settings do not match your taste. Chances are you totally dislike some of the ideas behind this generator. In those cases you always can edit the .yo-rc.json with settings more to your taste.
The default configuration looks like this:
// .yo-rc.json
// appended suffix for test files
testSuffix: '.spec',
// if your specs should succeed after creation or not
testPassOnDefault: true,
// always use defaults, never open prompts
alwaysSkipDialog: false,
// you can set this to '' if you prefer loading your styles dynamically
stylePrefix: '_',
// command launched when using the --openInEditor flag. The command
// looks like this <%= editorCommand %> file-created1.ext file-created2.ext etc.
editorCommand: 'idea',
// default style of path names, e.g. for dasherize:
// yo moda:c MyRoute
// => my-route-c.js
pathOutputStyle: 'dasherize',
// if ui router is enabled or not. If you want to enable this option,
// make sure you also have dirs.routes and routesFile defined
uiRouter: false,
// the file where the states get injected
routesFile: './app/scripts/_routes.js',
// file extensions used. At the moment there are only templates
// available for those specified per default. So don't change them
fileExt: {
script: '.js',
tpl: '.html',
style: '.scss'
},
// some folders used in the creation process
dirs: {
app: 'app',
appModules: 'scripts',
globalComponents: '_main',
routes: '_routes'
},
// here it gets interesting...
subGenerators: {
directive: {
suffix: '-d',
// directory used when no file-path is specified
globalDir: '',
// create a parent directory with the same name, e.g.:
// yo moda:d bla
// => bla/bla-d.js for true
// => bla-d.js for false
createDirectory: true
},
controller: {
suffix: '-c',
// extensions for the name of the component, e.g:
// yo moda:c bla
// => Controller-name = 'BlaCtrl'
nameSuffix: 'Ctrl',
globalDir: '',
createDirectory: true,
},
component: {
suffix: '-cp',
globalDir: '',
createDirectory: true
},
service: {
suffix: '-s',
globalDir: '_main/global-services'
},
factory: {
suffix: '-f',
globalDir: '_main/global-services'
},
filter: {
suffix: '-filter',
globalDir: '_main/global-filters'
},
provider: {
suffix: '-p',
globalDir: '_main/global-services'
},
decorator: {
suffix: '-decorator',
globalDir: '_main/global-services'
},
mod: {
// there are also prefixes available
prefix: '_',
createDirectory: true
}
}
You can also use your own templates!
To distinguish files (e.g. in your awesome file searcher) they're su- and prefixed by the following rules:
_*/ : main app directories main and route
are prefixed to be shown on top
_*.js : angular module, prefixed like this to be
loaded first by file-injection
*.spec.js : unit tests of all kind
*.e2e.js : end to end tests
*-c.js : controller
*-c.html : controller-template
_*-c.scss : route-specfic (usually page-level) styles
*-d.js : directive
*-d.html : directive-template
_*-d.scss : directive-specific (usually component-level) styles
*-f.js : factory
*-s.js : service
*-p.js : provider
*-filter.js : filter
*-decorator.js : decorator
This is a list of files which get create by moda:app
at the root level
.bowerrc
.editorconfig
.gitignore
.jshintrc
.travis.yml
bower.json
config.xml
gulpfile.js
karma.conf.js
karma-e2e.conf.js
package.json
README.md
app/
index.html
bower_components/ // ignored
fonts/
images/
styles/
_variables.scss
main.scss // should not be edited manually as
it is used for importing all files
mixins/
functions/
placeholders/
base/
_buttons.scss
_fonts.scss
_forms.scss
_icons.scss
_lists.scss
_page.scss
_tables.scss
_typography.scss
scripts/
_app.js
_app.spec.js
routes.js // if using ui.router
routes.spec.js // if using ui.router
_routes/ // if using ui.router
e2e-tests/
po/ // page-objects
example.e2e.js
node_modules/ // ignored
tasks/
build.js
config.js
cordova.js
deploy.js
dev.js
www/ // dist folder - ignored
Compiling your app to a hybrid app requires a little bit of configuration and you need to have cordova installed. Fortunately that is quite easy.
If everything is in place, you need to add the platforms you want to build your app on. For Android you would run:
cordova platform add android
If you get the message Current working directory is not a Cordova-based project
you need to create the www-folder first (e.g.: mkdir www
from your projects root directory).
After that you should build your current state via gulp build
then you can run gulp run
or gulp emulate
to check out your app on your device or in the emulator.
Yap, its possible. I wrote a wiki-article on how I did it on Ubuntu with IntelliJ. And for those who didn't know: There is a video by John Lindquist for those of you lucky enough having no path issues with node on your machine.
Using a simple syntax templates can be overwritten inside the .yo-rc.json:
// .yo-rc.json
"subGenerators": {
"directive": {
"tpl": {
// the html file, if any for that sub-generatr
"tpl": "<div>I love divs</div>"
// the test file
"spec": "console.log('empty tests are the best');"
// the main script file
// notice that all common variables are also
// available here
"script": "angular.module(<%= scriptAppName %>).directive('<%= cameledName %><%= nameSuffix %>',
"style": ".<%=sluggedName %>{color:green}
}
}
}
If you want to make more than some small adjustments, than you might prefer setting a custom template path.
The customTemplatesPath
can be specified from the project root directory (which is determined by where the .yo-rc.json is)
"customTemplatesPath": "yo-rc-templates"
or even use a absolute path, e.g.:
"customTemplatesPath": "/home/user/barney/templates-that-not-suck"
If you place a service.js and a service.spec.js in the your-app/yo-rc-templates/
-folder. Those templates would be used, instead of default ones. If you do this make sure that all the files of the sub-generators you want to use are present there.
The templates are compiled via underscore templates. There are some additional variables available which might be useful to you:
<%= cameledName %>
: cameled name, e.g.:yo moda:s test-name
would returntestName
<%= classedName %>
: classed name, e.g.:yo moda:s test-name
would returnTestName
<%= sluggedName %>
: slugged name, e.g.:yo moda:s test-name
would returntest-name
<%= dashedName %>
: dashed name, e.g.:yo moda:s test-name
would returntest-name
<%= nameSuffix %>
: the suffix defined for the current template in the .yo-rc.json, e.g.:yo moda:c test-name
would returnCtrl
with the default config<%= svcName %>
: the full name of a service created withyo moda:c
oryo moda:d
, e.g.:yo moda:d test-name
would returnTestName
with the default settings.<%= scriptAppName %>
: name of the app as used in the module declaration of the _app.js<%= createdFiles %>
: an array with objects with the created files and the used templates by the current sub-generator
In addition you can use basically all variables set in the .yo-rc.json. If you want to define your own ones it is smart to use a specific property for that, e.g.
// .yo-rc.json
{
// basic config vars
// ...
"yourVariables": {
"someVar": "someVal",
"someOtherVar": "someVal",
}
}