generator-ng-poly
Yeoman generator for modular AngularJS apps with Gulp and optional Polymer support
Inspired by John Papa's Angular Style Guide and Todd Motto's AngularJS styleguide.
Purpose
This generator focuses on organizing Angular components by feature (home, about, video player, etc.) instead of by type (controller, service, directive, etc.) to encourage the development of self-contained, reusable components.
A typical workflow with this generator consists of creating an Angular module (ng-poly:module) and then generating controllers, directives, etc. for this module to create a new feature.
Polymer is just an added feature, but it isn't required to utilize this generator.
Usage
Install generator-ng-poly
:
npm install -g bower gulp yo generator-ng-poly
If TypeScript is going to be used, tsd
will need to be installed:
npm install -g tsd
Run yo ng-poly
Yeoman will then ask for an app name and language preferences.
If using Node 0.12, there is a bug in Yeoman or Node causing yeoman generators to hang. With ng-poly, if after it outputs the generated home module files it hangs, then it is safe to enter Ctrl+C
, etc. The project is good to go and everything else should work normally.
Run gulp
to build and start the development environment. More detail on Gulp tasks
User Groups
Please feel free to ask any questions on our GitHub Issues or Google Group.
Generators
Available generators:
- AngularJS
- Polymer
Languages and Features supported:
- Angular Versions
- 1.2.*, 1.3.*, 1.4.*
- Markup
- HAML, HTML, Jade
- Application scripting languages
- CoffeeScript, EcmaScript2015 (ES6) with Babel, JavaScript (ES5), TypeScript
- Testing scripting languages
- CoffeeScript, EcmaScript2015 (ES6) with Babel, JavaScript (ES5), TypeScript†
- Style languages
- CSS, LESS, SCSS, Stylus
- Routers
- Angular Route, UI Router
- Unit testing
- Jasmine (Karma as the test runner) for AngularJS
- Mocha with Chai (Karma as the test runner) for AngularJS
- e2e testing
- Jasmine (ran with Protractor) for AngularJS
- Mocha, Chai, and Chai as Promised (ran with Protractor) for AngularJS
- Frameworks (scaffolds simple navbar)
- Angular Material (1.3.* or higher only)
- Doesn't scaffold navbar, yet
- Bootstrap with AngularStrap
- Bootstrap with UI Bootstrap
- Foundation with Angular Foundation
- Angular Material (1.3.* or higher only)
- Polymer
- Core, Paper
- Task runners
- Gulp
- Other supported Bower packages:
- Angular Animate
- Angular Cookies
- Angular Messages
- Angular Resource
- Angular Sanitize
- Angular Touch
- Font Awesome
- Lo-Dash
- Restangular
† e2e tests are not supported in TypeScript. JavaScript will instead be used for e2e tests.
Gulp Tasks Briefing
gulp
will start a localhost and open in the default browser
Using --stage prod
will concat and minify HTML, CSS, and Angular modules.
gulp build
will compile the assets
gulp dev
will call the build task and setup the development environment
gulp unitTest
will run unit tests via Karma and create code coverage reports
gulp webdriverUpdate
will download the Selenium server standalone and Chrome driver for e2e testing
gulp e2eTest
will run e2e tests via Protractor (must start a localhost before running gulp e2eTest
)
All generators ask for a module name except app and element. All generators except app take a name as an argument. A name can be written with CamelCase or hyphens.
Generators requiring a module can take a module option to bypass the prompt:
yo ng-poly:view newView --module=home/kitchen
A module value of app
will add the new components to the module defined in app.js or app.coffee.
Examples are shown with HTML, LESS, JavaScript, Jasmine, and UI Router as the app configuration.
App
Asks for application name and language preferences to scaffold out an application with a home module. It will also ask if tests should be placed in the app/
or tests/
directory. It'll ask for some additional Bower dependencies and then install npm and Bower dependencies.
Example:
Run yo ng-poly
to get started. ng-poly will then asks you some questions:
[?] What is the app's name?
[?] Which version of Angular should be used?
[?] Which structure should be used?
[?] Which is the preferred markup language?
[?] Which is the preferred application scripting language?
[?] Want to use Controller As syntax?
[?] Should directives be generated using a templateUrl (and markup file) instead of an inline template?
[?] By default, should the route generator create controllers?
[?] Which is the preferred test scripting language?
[?] Which is the preferred unit testing framework?
[?] Which is the preferred e2e testing framework?
[?] Which is the preferred style language?
[?] Should Polymer support be enabled?
[?] Should a framework be setup?
[?] Should ngRoute be used instead of UI Router?
[?] Which additional Bower components should be installed?
ng-poly makes some assumptions, but these can be overridden.
Option | Default Value | Info |
---|---|---|
host | localhost | BrowserSync and Protractor will use this host. |
port | 3000 | BrowserSync and Protractor will use this port. |
app-dir | app | Source code will be generated here. |
unit-test-dir | app | Unit tests will be generated here. |
skip-controller | false | Should the route generator skip creating a controller? |
skip-install | false | Should ng-poly skip installing Bower and npm dependencies? |
Example: yo ng-poly --port=8080 --app-dir=src
to override the default port and app directory.
A module-only structure produces:
root/
├── app/
│ ├── fonts/ (empty)
│ ├── home/
│ │ ├── home-module.{coffee,es6,js,ts}
│ │ ├── home-routes.{coffee,es6,js,ts}
│ │ ├── home.{css,less,scss,styl}
│ │ ├── home.tpl.{haml,html,jade}
│ │ ├── home-controller.{coffee,es6,js,ts}
│ │ └── home-controller_test.{coffee,es6,js,ts}
│ ├── images/ (empty)
│ ├── app-module.{coffee,es6,js,ts}
│ ├── app-routes.{coffee,es6,js,ts}
│ └── index.{haml,html,jade}
├── bower_components/
├── e2e/
│ └── home/
│ ├── home.po.{coffee,es6,js}
│ └── home_test.{coffee,es6,js}
├── gulp/
│ ├── analyze.js
│ ├── build.js
│ ├── test.js
│ └── watch.js
├── node_modules/
├── typings/*
├── .bowerrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .yo-rc.json
├── bower.json
├── build.config.js
├── Gulpfile.js
├── karma.config.js
├── package.json
├── protractor.config.js
├── README.md
├── tsd.json*
└── tslint.json*
A module-type structure produces:
root/
├── app/
│ ├── fonts/ (empty)
│ ├── home/
│ │ ├── controllers/
│ │ │ ├── home-controller.{coffee,es6,js,ts}
│ │ │ └── home-controller_test.{coffee,es6,js,ts}
│ │ ├── views/
│ │ │ ├── home.{css,less,scss,styl}
│ │ │ └── home.tpl.{haml,html,jade}
│ │ ├── home-module.{coffee,es6,js,ts}
│ │ └── home-routes.{coffee,es6,js,ts}
│ ├── images/ (empty)
│ ├── app-module.{coffee,es6,js,ts}
│ ├── app-routes.{coffee,es6,js,ts}
│ └── index.{haml,html,jade}
├── bower_components/
├── e2e/
│ └── home/
│ ├── home.po.{coffee,es6,js}
│ └── home_test.{coffee,es6,js}
├── gulp/
│ ├── analyze.js
│ ├── build.js
│ ├── test.js
│ └── watch.js
├── node_modules/
├── typings/*
├── .bowerrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .yo-rc.json
├── bower.json
├── build.config.js
├── Gulpfile.js
├── karma.config.js
├── package.json
├── protractor.config.js
├── README.md
├── tsd.json*
└── tslint.json*
* Only TypeScript projects will have this.
Constant
Generates a constant and its test.
Example:
yo ng-poly:constant theHero
[?] Which module is this for?
Produces app/module/the-hero-constant.js
:
{ 'use strict'; /** * @ngdoc service * @name module.constant:theHero * * @description * */ angular ;};
Produces app/module/the-hero-constant_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Controller
Genrates a controller and its test.
Example:
yo ng-poly:controller micro
[?] Which module is this for?
Produces app/module/micro-controller.js
:
{ 'use strict'; /** * @ngdoc object * @name module.controller:MicroCtrl * @requires $scope * * @description * */ angular ; { $scopemicro = {}; $scopemicroctrlName = 'MicroCtrl'; }};
Produces app/module/micro-controller_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Decorator
Generates a decorator and its test.
Example:
yo ng-poly:decorator awesomeService
[?] Which module is this for?
Note: If decorating a service starting with a $
you must escape it like:
yo ng-poly:decorator \$state
Produces app/module/awesome-service-decorator.js
:
{ 'use strict'; /** * @ngdoc decorator * @name home.decorator:awesomeService * @restrict EA * @element * * @description * */ angular ; { $provide; }};
Produces: app/module/awesome-service-decorator_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Directive
Generates a directive, its template, and its test.
Example:
yo ng-poly:directive fancy-button
[?] Which module is this for?
Produces app/module/fancy-button-directive.js
:
{ 'use strict'; /** * @ngdoc directive * @name module.directive:fancyButton * @restrict EA * @element * * @description * * @example <example module="module"> <file name="index.html"> <fancy-button></fancy-button> </file> </example> * */ angular ; { return restrict: 'EA' scope: {} templateUrl: 'module/fancy-button-directive.tpl.html' replace: false { scopefancyButton = {}; scopefancyButtonname = 'fancyButton'; } { /*jshint unused:false */ /*eslint "no-unused-vars": [2, {"args": "none"}]*/ } ; }};
Produces app/module/fancy-button-directive.tpl.html
:
{{fancyButton.name}}
Produces app/module/fancy-button-directive_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
The directive's template (HAML, HTML, or Jade) is converted to a temporary module automatically for testing.
Factory
Generates a factory and its test.
Example:
yo ng-poly:factory cake
[?] Which module is this for?
Produces app/module/cake-factory.js
:
{ 'use strict'; /** * @ngdoc service * @name module.factory:Cake * * @description * */ angular ; { var CakeBase = {}; CakeBasesomeValue = 'Cake'; CakeBase { return 'Cake'; }; return CakeBase; }};
Produces app/module/Cake-factory_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Filter
Generates a filter and its test.
Example:
yo ng-poly:filter coffee
[?] Which module is this for?
Produces app/module/coffee-filter.js
:
{ 'use strict'; /** * @ngdoc filter * @name module.filter:coffee * * @description * * @param * @returns * */ angular ; { return { var temp = ; angular; return temp; }; }};
Produces app/module/coffee-filter_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Module
Generates a new module and create a new route. Updates parent module's dependencies.
Top Level Example:
yo ng-poly:module top
Produces app/top/top-module.js
:
{ 'use strict'; /* @ngdoc object * @name top * @description * */ angular ;};
Produces pp/top/top-routes.js
:
{ 'use strict'; angular ; { $stateProvider ; }};
Produces app/top/top-controller.js
, app/top/top-controller_test.js
, app/top/top.tpl.html
, app/top/top.less
, e2e/top/top.po.js
, e2e/top/top_test.js
Updates app/app-module.js
:
{ 'use strict'; /* @ngdoc object * @name module * @requires $urlRouterProvider * @description * */ angular ;};
Deep Level Example:
yo ng-poly:module top/bottom
Produces app/top/bottom/bottom-module.js
, app/top/boottom/bottom-routes.js
, app/top/bottom/bottom-controller.js
, app/top/bottom/bottom-controller_test.js
, app/top/bottom/bottom.tpl.html
, app/top/bottom/bottom.less
, e2e/bottom/bottom.po.js
, e2e/bottom/bottom_test.js
Updates app/top/top-module.js
:
{ 'use strict'; /* @ngdoc object * @name top * @requires $stateProvider * * @description * */ angular ;};
Notice the module in app/top/bottom/
is called 'top.bottom'. All tests in this directory use this nomenclature, as well.
Deeper Level Example:
yo ng-poly:module top/bottom/bottomest
Produces 'bottom.bottomest' module and routes, a controller, controller test, style, and a view in app/top/bottom/bottomest/
Updates 'top.bottom' module with the new 'bottom.bottemest' module as a dependency.
Deeperestier Level Example:
It just keeps going...
Empty modules
By running ng-poly:module newHome --empty
a module's routes file will not be created.
The module file will omit the router dependency:
{ 'use strict'; /* @ngdoc object * @name newHome * * @description * */ angular ;};
Provider
Generates a provider and its test.
Example:
yo ng-poly:provider bacon
[?] Which module is this for?
Produces app/module/bacon-provider.js
:
{ 'use strict'; /** * @ngdoc service * @name module.provider:Bacon * * @description * */ angular ; { return $ { return 'Bacon'; } ; }};
Produces app/module/Bacon-provider_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Route
Adds a new route and generates a controller and view. The name provided is used as state name for UI Router and as the route URL for ngRoute. Yeoman will then ask for the module to add the route to, the URL for the route (UI Router only), and the templateUrl. It will also generate an e2e test and a Page Object model for the new route.
Example:
yo ng-poly:route your-place
[?] Which module is this for?
[?] What's the URL for this route? (UI Router only)
[?] What's the templateURL for this route?
Updates app/module/module-module.js
:
{ 'use strict'; /* @ngdoc object * @name module * @requires $stateProvider * * @description * */ angular ; angular ; { $stateProvider ; }};
Produces e2e/your-place/your-place.po.js
:
/*global element, by*/'use strict'; var { thistext = ; thisheading = ;}; moduleexports = YourPlacePage;
Produces e2e/your-place/your-place_test.js
:
/*global describe, beforeEach, it, browser, expect */'use strict'; var buildConfigFile = 'build.config.js' buildConfig = YourPlacePagePo = ; ;
Produces app/module/your-place-controller.js
, app/module/your-place-controller_test.js
, app/module/your-place.tpl.html
, and app/module/your-place.less
Currently, the module must have an existing state for another to be added.
The route generator can take URL and templateUrl options, as well.
yo ng-poly:route yourPlace --url=yourPlace --template-url=your-place
The URL will automatically be prepended with /
and and the templateUrl will be appended with .tpl.html
.
Service
Generates a service and its test.
Example:
yo ng-poly:service cheap-or-good
[?] Which module is this for?
Produces app/module/cheap-or-good-service.js
:
{ 'use strict'; /** * @ngdoc service * @name home.service:CheapOrGood * * @description * */ angular ; { var self = this; self { return 'CheapOrGood'; }; }};
Produces app/module/cheap-or-good-service_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Value
Generates a value and its test.
Example:
yo ng-poly:value morals
[?] Which module is this for?
Produces app/module/morals-value.js
:
{ 'use strict'; /** * @ngdoc service * @name module.constant:morals * * @description * */ angular value'morals' 0;};
Produces app/module/morals-value_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
View
Generates a view and its style.
Example:
yo ng-poly:view nice
[?] Which module is this for?
Produces app/module/nice-view.tpl.html
:
nice{{nice.ctrlName}}
Produces an empty file app/module/nice-view.less
Element
Generates a Polymer element.
Example:
yo ng-poly:element gold-silver
Produces app/components/gold-silver/gold-silver.less
:
Produces app/components/gold-silver/gold-silver.html
:
{{name}}
Produces app/components/gold-silver/gold-silver.js
:
/*global Polymer*/ { 'use strict'; var element = is: 'gold-silver' { console; } ; return element;};
Configurations
It is possible to override some configurations initially specified when yo ng-poly
was ran.
Each generator is able to take the following arguments. For example, yo ng-poly:module test --skip-controller=true
will override the configuration settings for everything generated by this command.
Option | Possible Values | Description |
---|---|---|
directive-template-url | true, false | Use external markup files and templateUrl instead of template in directives |
skip-controller | true, false | Skip creating controllers when generating routes and modules |
It's not recommended to mix ngRoute and UI Router, but it's possible.
Controller As Syntax
This generator has support for the Controller As syntax. Yeoman will ask if this should be enabled when ng-poly:app
is ran.
This will generate controllers like:
{ 'use strict'; /** * @ngdoc object * @name home.controller:HomeCtrl * * @description * */ angular ; { var vm = this; vmctrlName = 'HomeCtrl'; }};
...and their tests like:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
It'll also modify the state's controller like:
{ 'use strict'; /* @ngdoc object * @name home * @requires $stateProvider * * @description * */ angular ; angular ; { $stateProvider ; }};
Directives will be generated like:
{ 'use strict'; /** * @ngdoc directive * @name home.directive:fancyButton * @restrict EA * @element * * @description * * @example <example module="home"> <file name="index.html"> <fancy-button></fancy-button> </file> </example> * */ angular ; { return restrict: 'EA' scope: {} templateUrl: 'home/fancy-button-directive.tpl.html' replace: false controllerAs: 'fancyButton' { var vm = this; vmname = 'fancyButton'; } { /*jshint unused:false */ /*eslint "no-unused-vars": [2, {"args": "none"}]*/ } ; }};
Lastly, views will be generated like:
home{{home.ctrlName}}
Directive TemplateUrl
This generator has supporting for creating directives with inline templates and external markup files using templateUrl.
If Directive TemplateUrl is enabled, directives will be created as described above.
If Directive TemplateUrl is disabled, directives will be created like below.
Example:
yo ng-poly:directive fancy-button
[?] Which module is this for?
Produces app/module/fancy-button-directive.js
:
{ 'use strict'; /** * @ngdoc directive * @name module.directive:fancyButton * @restrict EA * @element * * @description * * @example <example module="module"> <file name="index.html"> <fancy-button></fancy-button> </file> </example> * */ angular ; { return restrict: 'EA' scope: {} template: '<div>{{fancyButton.name}}</div>' replace: false { scopefancyButton = {}; scopefancyButtonname = 'fancyButton'; } { /*jshint unused:false */ /*eslint "no-unused-vars": [2, {"args": "none"}]*/ } ; }};
Produces app/module/fancy-button-directive_test.js
:
/*global describe, beforeEach, it, expect, inject, module*/'use strict'; ;
Gulp Tasks in Detail
Items in italics are only ran in --stage=prod
Available tasks:
gulp
orgulp default
- Runs
gulp dev
gulp dev
- Runs
gulp build
and starts BrowserSync and Gulp's watch gulp build
- Runs
gulp analyze
- Runs
gulp clean
to delete build directory - Runs
gulp markup
to compile Haml and Jade to HTML - Runs
gulp styles
to compile Less, SCSS, and Stylus (with Nib), add vendor prefixes, modify images and font URLs, concat, minify, and rev - Runs
gulp scripts
to compile ES2015, CoffeeScript, and TypeScript, injects HTML templates in $templateCache, sorts Angular files, annotates, minifies, and rev - Runs
gulp inject
to inject sorted JS and CSS source files into build/index.html - Runs
gulp bowerCopy
to modify image and font URLs in vendor CSS files, concat vendor CSS, minify vendor CSS, rev vendor CSS, copy vendor CSS to build, concat vendor JS, minify vendor JS and leave licenses intact, copy vendor JS to build - Runs
gulp bowerInject
to inject vendor CSS and JS into build/index.html - Runs
gulp bowerAssets
to copy over any vendor image and fonts to build/ - Runs
gulp fonts
to copy app fonts to build/ - Runs
gulp images
to copy app images to build/ - Runs
gulp copyTemplates
to copy compiled templates to a separate test directory used for unit testing - Runs
gulp deleteTemplates
to delete templates in build gulp unitTest
- Runs
gulp lint
- Runs
gulp clean:test
to delete previous compiled unit tests - Runs
gulp buildTests
to compile CoffeeScript, ES2015, and TypeScript unit tests - Runs
gulp build
- Runs
gulp karmaFiles
to automatically configure Bower dependencies, directive templates, sorted build JS files, and unit tests for Karma - Run unit tests with Karma
gulp e2eTest
- Runs
gulp lint
- Runs
gulp build
- Runs
gulp build:e2eTest
to compile CoffeeScript, ES2015, and TypeScript tests - Runs Protractor to perform e2eTest (app needs to be running via
gulp default
) gulp webdriverUpdate
downloads Selenium and webdrivers for e2e testinggulp analyze
- Analyzes source and test code with CoffeeLint, TSLint, ESLint, JSHint, and JSCS
- Uses Plato to inspect source and test for complexity and maintainability
How to Add Polymer elements
Currently, this process isn't as simple as desired. Pull requests are greatly appreciated to help this process in any way.
For this, we're going to install and setup paper-toolbar
.
- Run
bower install --save polymerelements/paper-toolbar
to downloadpaper-toolbar
and its dependencies. - Digging through
bower_components/paper-toolbar/
, we can determine it needs to havepaper-toolbar/paper-toolbar.html
,paper-styles/paper-styles.html
, andpolymer/polymer
. We then dig throughbower_components/paper-styles/
to find it needspaper-styles/paper-styles.html
,paper-styles/color.html
,paper-styles/default-theme.html
,paper-styles/shadow.html
,paper-styles/typography.html
,iron-flex-layout/iron-flex-layout.html
, andiron-flex-layout/classes/iron-flex-layout.html
. Then we dig throughiron-flex-layout/iron-flex-layout
to find out we just need to additionally includeiron-flex-layout/iron-flex-layout/classes/iron-shadow-flex-layout.html
. - Edit the
components
task ingulp/build.js
to include polymerBowerAssetsToCopy like:
polymerBowerAssetsToCopy = 'polymer/polymer*.html' 'iron-flex-layout/iron-flex-layout.html' 'iron-flex-layout/classes/*.html' 'paper-styles/{color,default-theme,paper-styles,shadow.html,typography.html}' 'paper-toolbar/paper-toolbar.html';
- Edit the
componentsInject
task ingulp/build.js
to include
var polymerAssetsToInject = 'polymer/polymer.html' 'paper-styles/paper-styles.html';
- Now, we can use the
<paper-toolbar>
element in our code.
Note: for custom components, we just need to include them in polymerAssetsToInject
. They are all automically copied.
Contributors
Thanks goes to these wonderful people (emoji key):
Dustin Specker 💻 📖 ⚠️ |
Binh Nguyen 💻 📖 ⚠️ |
Roman C. Podolski 💻 |
David Brown 💻 📖 |
David Steinkopff 💻 |
Frode Egeland 💻 ⚠️ |
Scott Busche 💻 |
---|---|---|---|---|---|---|
Rafael Zeffa 💻 ⚠️ |
PatrickJS 📖 |
Tiago de Assis Gonçalves 💻 |
Ruslan Stelmachenko 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!
License
MIT