├── .gitattributes ├── app ├── templates │ └── root │ │ ├── .bowerrc │ │ ├── src │ │ ├── app │ │ │ ├── home │ │ │ │ ├── home.less │ │ │ │ ├── home.tpl.html │ │ │ │ ├── _home.module.coffee │ │ │ │ ├── _home.spec.coffee │ │ │ │ ├── _home.spec.js │ │ │ │ ├── home.module.js │ │ │ │ ├── _home.coffee │ │ │ │ └── _home.js │ │ │ ├── about │ │ │ │ ├── about.tpl.html │ │ │ │ ├── about.coffee │ │ │ │ ├── about.module.coffee │ │ │ │ ├── about.js │ │ │ │ └── about.module.js │ │ │ ├── _app.coffee │ │ │ ├── _app.js │ │ │ ├── app.spec.coffee │ │ │ ├── app.spec.js │ │ │ ├── README.md │ │ │ └── _mockApp.js │ │ ├── assets │ │ │ └── README.md │ │ ├── less │ │ │ ├── variables.less │ │ │ ├── main.less │ │ │ └── README.md │ │ ├── common │ │ │ └── README.md │ │ ├── _index.html │ │ └── _README.md │ │ ├── _README.md │ │ ├── .gitignore │ │ ├── _bower.json │ │ ├── _package.json │ │ ├── karma │ │ └── karma-unit.tpl.js │ │ └── _Gruntfile.js └── index.js ├── module ├── templates │ ├── _module.less │ ├── _moduleSpec.coffee │ ├── _module.coffee │ ├── _moduleAdd.tpl.html │ ├── _moduleSpec.js │ ├── directives │ │ ├── _moduleForm.tpl.html │ │ └── _moduleForm.js │ ├── _moduleEdit.tpl.html │ ├── _moduleService.js │ ├── _module.module.coffee │ ├── _moduleList.tpl.html │ ├── _moduleAdd.js │ ├── _module.js │ ├── _moduleEdit.js │ └── _module.module.js └── index.js ├── .travis.yml ├── .editorconfig ├── test ├── test-load.js └── test-creation.js ├── .jshintrc ├── .gitignore ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /app/templates/root/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor" 3 | } -------------------------------------------------------------------------------- /module/templates/_module.less: -------------------------------------------------------------------------------- 1 | /* Less file for <%= camelModuleName %>*/ -------------------------------------------------------------------------------- /app/templates/root/src/app/home/home.less: -------------------------------------------------------------------------------- 1 | .homeClass { 2 | margin: 0 0 0 0; 3 | } -------------------------------------------------------------------------------- /app/templates/root/src/app/about/about.tpl.html: -------------------------------------------------------------------------------- 1 |

About

2 | 3 |

This is what this is about.

-------------------------------------------------------------------------------- /app/templates/root/_README.md: -------------------------------------------------------------------------------- 1 | # <%= _.humanize(projectName) %> 2 | 3 | A simplified version of the ngBoilerplate angular project template. 4 | 5 | *** 6 | 7 | ## Quick Start 8 | -------------------------------------------------------------------------------- /app/templates/root/src/assets/README.md: -------------------------------------------------------------------------------- 1 | # The `src/assets` Directory 2 | 3 | There's really not much to say here. Every file in this directory is recursively transferred to `dist/assets/`. 4 | 5 | -------------------------------------------------------------------------------- /module/templates/_moduleSpec.coffee: -------------------------------------------------------------------------------- 1 | describe '<%= camelModuleName %> section', -> 2 | beforeEach(module '<%= projectName %>.<%= camelModuleName %>') 3 | 4 | it('should have a dummy test', inject -> 5 | expect(true).toBeTruthy() 6 | ) 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - currentfolder=${PWD##*/} 6 | - if [ "$currentfolder" != 'generator-ngbp' ]; then cd .. && eval "mv $currentfolder generator-ngbp" && cd generator-ngbp; fi 7 | 8 | -------------------------------------------------------------------------------- /module/templates/_module.coffee: -------------------------------------------------------------------------------- 1 | do (module=angular.module "<%= projectName %>.<%= camelModuleName %>") -> 2 | module.controller '<%= capitalModuleName %>Controller', () -> 3 | model = this 4 | 5 | init = -> 6 | # Initialize 7 | 8 | init() 9 | 10 | -------------------------------------------------------------------------------- /module/templates/_moduleAdd.tpl.html: -------------------------------------------------------------------------------- 1 |

Add <%= resourceName %>

2 |
-form="model.<%= resourceInstance %>">
3 | 4 | Cancel -------------------------------------------------------------------------------- /module/templates/_moduleSpec.js: -------------------------------------------------------------------------------- 1 | describe('<%= camelModuleName %> section', function () { 2 | beforeEach(module('<%= projectName %>.<%= camelModuleName %>')); 3 | 4 | it('should have a dummy test', inject(function() { 5 | expect(true).toBeTruthy(); 6 | })); 7 | }); -------------------------------------------------------------------------------- /module/templates/directives/_moduleForm.tpl.html: -------------------------------------------------------------------------------- 1 |

<%= resourceName %> Form

2 |
3 | 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /module/templates/_moduleEdit.tpl.html: -------------------------------------------------------------------------------- 1 |

Edit <%= resourceName %>

2 |
-form="model.<%= resourceInstance %>">
3 | 4 | Cancel -------------------------------------------------------------------------------- /test/test-load.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it*/ 2 | 'use strict'; 3 | var assert = require('assert'); 4 | 5 | describe('ngbp generator', function () { 6 | it('can be imported without blowing up', function () { 7 | var app = require('../app'); 8 | assert(app !== undefined); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /app/templates/root/src/less/variables.less: -------------------------------------------------------------------------------- 1 | /** 2 | * These are the variables used throughout the application. This is where 3 | * overwrites that are not specific to components should be maintained. 4 | */ 5 | 6 | 7 | 8 | /** 9 | * Typography-related. 10 | */ 11 | 12 | @sansFontFamily: 'Roboto', sans-serif; -------------------------------------------------------------------------------- /app/templates/root/src/app/home/home.tpl.html: -------------------------------------------------------------------------------- 1 |

Home of <%= _.humanize(projectName) %>

2 | 3 |

Code it up

4 | 5 |

6 | 7 |

10 | 11 |

-------------------------------------------------------------------------------- /module/templates/_moduleService.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | module.factory('<%= resourceName %>', function($resource) { 3 | return $resource( 4 | '/api/<%= lowerModuleName %>/:id', 5 | { id: '@id' }, 6 | { 'update': {method: 'PUT'} } 7 | ); 8 | }); 9 | })(angular.module("<%= projectName %>.<%= camelModuleName %>")); 10 | -------------------------------------------------------------------------------- /app/templates/root/src/app/about/about.coffee: -------------------------------------------------------------------------------- 1 | do (module=angular.module "<%= projectName %>.about") -> 2 | module.controller 'AboutController', () -> 3 | model = this 4 | 5 | init = -> 6 | # A definitive place to put everything that needs to run 7 | # when the controller starts. Avoid writing any code outside 8 | # of this function that executes immediately. 9 | 10 | init() 11 | 12 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/_home.module.coffee: -------------------------------------------------------------------------------- 1 | do (module=angular.module "<%= projectName %>.home", [ 2 | 'ui.router' 3 | ]) -> 4 | module.config ($stateProvider) -> 5 | $stateProvider.state 'home', 6 | url: '/home' 7 | views: 8 | "main": 9 | controller: 'HomeController as model' 10 | templateUrl: 'home/home.tpl.html' 11 | data: 12 | pageTitle: 'Home' 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/templates/root/src/app/about/about.module.coffee: -------------------------------------------------------------------------------- 1 | do (module=angular.module "<%= projectName %>.about", [ 2 | 'ui.router' 3 | ]) -> 4 | module.config ($stateProvider) -> 5 | $stateProvider.state 'about', 6 | url: '/about' 7 | views: 8 | "main": 9 | controller: 'AboutController as model' 10 | templateUrl: 'about/about.tpl.html' 11 | data: 12 | pageTitle: 'About' 13 | 14 | 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /app/templates/root/src/app/_app.coffee: -------------------------------------------------------------------------------- 1 | do (app=angular.module "<%= projectName %>", [ 2 | '<%= projectName %>.home', 3 | '<%= projectName %>.about', 4 | 'templates-app', 5 | 'templates-common', 6 | 'ui.router.state', 7 | 'ui.router', 8 | ]) -> 9 | 10 | app.config ($stateProvider, $urlRouterProvider) -> 11 | $urlRouterProvider.otherwise '/home' 12 | 13 | app.run -> 14 | 15 | app.controller 'AppController', ($scope) -> 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/_home.spec.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | # Tests sit right alongside the file they are testing, which is more intuitive 3 | # and portable than separating 'src' and 'test' directories. Additionally, the 4 | # build process will exclude all '.spec.js' files from the build 5 | # automatically. 6 | ### 7 | describe 'home section', -> 8 | beforeEach(module '<%= projectName %>.home') 9 | 10 | it('should have a dummy test', inject -> 11 | expect(true).toBeTruthy() 12 | ) 13 | -------------------------------------------------------------------------------- /app/templates/root/src/app/about/about.js: -------------------------------------------------------------------------------- 1 | (function(app) { 2 | 3 | app.controller('AboutController', function ($scope) { 4 | var model = this; 5 | 6 | init(); 7 | 8 | function init() { 9 | // A definitive place to put everything that needs to run when the controller starts. Avoid 10 | // writing any code outside of this function that executes immediately. 11 | } 12 | 13 | }); 14 | 15 | }(angular.module("<%= projectName %>.about"))); -------------------------------------------------------------------------------- /app/templates/root/.gitignore: -------------------------------------------------------------------------------- 1 | ~ 2 | build/ 3 | builds/ 4 | [Bb]in 5 | [Dd]ebug*/ 6 | obj/ 7 | [Rr]elease*/ 8 | zip/ 9 | _[Rr]e[Ss]harper*/ 10 | _[Rr]eSharper.* 11 | *.user 12 | *.[Rr]e[Ss]harper 13 | *.suo 14 | *.cache 15 | *.swp 16 | *.dot[Cc]over 17 | packages/* 18 | [Bb]ackup 19 | [Uu]pgrade[Ll]og.xml 20 | [Uu]pgrade[Ll]og.htm 21 | _[Uu]pgrade[Rr]eport_[Ff]iles 22 | .idea/ 23 | *.log 24 | *.log.* 25 | *node_modules* 26 | vendor/ 27 | vendor/* 28 | vendor/*/* 29 | vendor/*/*/* 30 | vendor/*/*/*/* 31 | 32 | 33 | -------------------------------------------------------------------------------- /module/templates/_module.module.coffee: -------------------------------------------------------------------------------- 1 | do (module=angular.module "<%= projectName %>.<%= camelModuleName %>", [ 2 | 'ui.router' 3 | ]) -> 4 | module.config ($stateProvider) -> 5 | $stateProvider.state '<%= name %>', 6 | url: '/<%= lowerModuleName %>' 7 | views: 8 | "main": 9 | controller: '<%= capitalModuleName %>Controller as model' 10 | templateUrl: '<%= path %>/<%= filePrefix %>.tpl.html' 11 | data: 12 | pageTitle: '<%= capitalModuleName %>' 13 | 14 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/_home.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests sit right alongside the file they are testing, which is more intuitive 3 | * and portable than separating 'src' and 'test' directories. Additionally, the 4 | * build process will exclude all '.spec.js' files from the build 5 | * automatically. 6 | */ 7 | describe('home section', function () { 8 | beforeEach(module('<%= projectName %>.home')); 9 | 10 | it('should have a dummy test', inject(function() { 11 | expect(true).toBeTruthy(); 12 | })); 13 | }); 14 | -------------------------------------------------------------------------------- /app/templates/root/src/app/_app.js: -------------------------------------------------------------------------------- 1 | (function(app) { 2 | 3 | app.config(function ($stateProvider, $urlRouterProvider) { 4 | $urlRouterProvider.otherwise('/home'); 5 | }); 6 | 7 | app.run(function () {}); 8 | 9 | app.controller('AppController', function ($scope) { 10 | 11 | }); 12 | 13 | }(angular.module("<%= projectName %>", [ 14 | '<%= projectName %>.home', 15 | '<%= projectName %>.about', 16 | 'templates-app', 17 | 'templates-common', 18 | 'ui.router.state', 19 | 'ui.router', 20 | ]))); 21 | -------------------------------------------------------------------------------- /app/templates/root/src/app/app.spec.coffee: -------------------------------------------------------------------------------- 1 | describe 'AppController', -> 2 | describe 'isCurrentUrl', -> 3 | AppCtrl = null 4 | 5 | beforeEach(module '<%= projectName %>') 6 | 7 | beforeEach(inject ($controller, _$location_, $rootScope) -> 8 | $location = _$location_ 9 | $scope = $rootScope.$new() 10 | AppCtrl = $controller 'AppController', 11 | $location: $location 12 | $scope: $scope 13 | ) 14 | 15 | it('should pass a dummy test', inject -> 16 | expect(AppCtrl).toBeTruthy() 17 | ) 18 | -------------------------------------------------------------------------------- /app/templates/root/src/app/about/about.module.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | module.config(function ($stateProvider) { 4 | $stateProvider.state('about', { 5 | url: '/about', 6 | views: { 7 | "main": { 8 | controller: 'AboutController as model', 9 | templateUrl: 'about/about.tpl.html' 10 | } 11 | }, 12 | data:{ pageTitle: 'About' } 13 | }); 14 | }); 15 | 16 | }(angular.module("<%= projectName %>.about", [ 17 | 'ui.router' 18 | ]))); 19 | -------------------------------------------------------------------------------- /module/templates/_moduleList.tpl.html: -------------------------------------------------------------------------------- 1 |

<%= capitalModuleName %>

2 | <% if (includeRest) { %> 3 | 11 | Add a <%= resourceName %> 12 | <% } %> -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ~ 2 | build/ 3 | builds/ 4 | [Bb]in 5 | */bin/* 6 | [Dd]ebug*/ 7 | obj/* 8 | [Rr]elease*/ 9 | _ReSharper*/ 10 | zip/ 11 | [Tt]est[Rr]esult 12 | [Bb]uild[Ll]og.* 13 | *.swp 14 | *.userprefs 15 | *.obj 16 | *.pdb 17 | *.user 18 | *.aps 19 | *.pch 20 | *.vspscc 21 | BuildLogs/* 22 | packages/* 23 | *.vssscc 24 | *.pidb 25 | *_i.c 26 | *_p.c 27 | *.ncb 28 | *.suo 29 | *.tlb 30 | *.tlh 31 | *.bak 32 | *.cache 33 | *.ilk 34 | *.log 35 | *.lib 36 | *.sbr 37 | *.scc 38 | *.zip 39 | .idea/ 40 | *.[Rr]e[Ss]harper 41 | *.[Pp]ublish.xml 42 | *-results.xml 43 | *.InstallLog 44 | [Ll]ogs/ 45 | packages/ 46 | *.dll 47 | node_modules/ 48 | -------------------------------------------------------------------------------- /app/templates/root/src/app/app.spec.js: -------------------------------------------------------------------------------- 1 | describe('AppController', function () { 2 | describe('isCurrentUrl', function () { 3 | var AppCtrl, $location, $scope; 4 | 5 | beforeEach(module('<%= projectName %>')); 6 | 7 | beforeEach(inject(function ($controller, _$location_, $rootScope) { 8 | $location = _$location_; 9 | $scope = $rootScope.$new(); 10 | AppCtrl = $controller('AppController', { $location: $location, $scope: $scope }); 11 | })); 12 | 13 | it('should pass a dummy test', inject(function () { 14 | expect(AppCtrl).toBeTruthy(); 15 | })); 16 | }); 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /app/templates/root/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= projectName %>", 3 | "version": "0.0.1", 4 | "_comment": "Any dependencies added here require a manual edit to vendor_files in /Gruntfile.js and (??? verify the following) /build/karma-unit.js (e.g 'vendor/angular-resource/angular-resource.js')", 5 | "devDependencies": { 6 | "angular": "1.3.15", 7 | <% if (includeAngularResource) {%>"angular-resource": "1.3.15",<% } %> 8 | "angular-mocks": "1.3.15", 9 | "bootstrap": "~3.3", 10 | "angular-bootstrap": "~0.12.0", 11 | "angular-ui-router": "~0.2.13" 12 | }, 13 | "dependencies": {}, 14 | "resolutions": { 15 | "angular": "1.3.15" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /module/templates/_moduleAdd.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | module.controller('Add<%= resourceName %>Controller', function ($window, <%= resourceName %>) { 4 | var model = this; 5 | model.<%= resourceInstance %> = new <%= resourceName %>(); 6 | model.save = save; 7 | 8 | init(); 9 | 10 | function init() { 11 | 12 | } 13 | 14 | function save() { 15 | model.<%= resourceInstance %>.$save() 16 | .then(function (data) { 17 | $window.location = '#/<%= lowerModuleName %>'; 18 | }) 19 | .catch(function (error) { 20 | console.log(error); 21 | }) 22 | .finally(); 23 | } 24 | }); 25 | 26 | }(angular.module("<%= projectName %>.<%= camelModuleName %>"))); 27 | -------------------------------------------------------------------------------- /module/templates/directives/_moduleForm.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | module.directive('<%= resourceInstance %>Form', function() { 3 | var linker = function(scope, element, attrs) { 4 | // do DOM Manipulation here 5 | }; 6 | return { 7 | restrict: 'A', 8 | templateUrl: '<%= lowerModuleName %>/directives/<%= resourceInstance %>Form.tpl.html', 9 | link: linker, 10 | controller: '<%= resourceName %>FormController as model', 11 | bindToController: true, 12 | scope: { 13 | <%= resourceInstance %>: '=<%= resourceInstance %>Form', 14 | lookups: '=<%= resourceInstance %>Lookups' 15 | } 16 | }; 17 | }); 18 | 19 | module.controller('<%= resourceName %>FormController', function() { 20 | var model = this; 21 | 22 | }); 23 | })(angular.module('<%= projectName %>.<%= camelModuleName %>')); -------------------------------------------------------------------------------- /app/templates/root/src/common/README.md: -------------------------------------------------------------------------------- 1 | # The `src/common/` Directory 2 | 3 | The `src/common/` directory houses internal and third-party re-usable 4 | components. Essentially, this folder is for everything that isn't completely 5 | specific to this application. 6 | 7 | Each component resides in its own directory that may then be structured any way 8 | the developer desires. The build system will read all `*.js` files that do not 9 | end in `.spec.js` as source files to be included in the final build, all 10 | `*.spec.js` files as unit tests to be executed, and all `*.tpl.html` files as 11 | templates to compiled into the `$templateCache`. There is currently no way to 12 | handle components that do not meet this pattern. 13 | 14 | ``` 15 | src/ 16 | |- common/ 17 | | |- someComponent/ 18 | ``` 19 | 20 | Every component contained here should be drag-and-drop reusable in any other 21 | project; they should depend on no other components that aren't similarly 22 | drag-and-drop reusable. 23 | -------------------------------------------------------------------------------- /test/test-creation.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, it */ 2 | 'use strict'; 3 | var path = require('path'); 4 | var helpers = require('yeoman-generator').test; 5 | 6 | describe('ngbp generator', function () { 7 | beforeEach(function (done) { 8 | helpers.testDirectory(path.join(__dirname, 'temp'), function (err) { 9 | if (err) { 10 | return done(err); 11 | } 12 | 13 | this.app = helpers.createGenerator('ngbp:app', [ 14 | '../../app' 15 | ]); 16 | done(); 17 | }.bind(this)); 18 | }); 19 | 20 | it('creates expected files', function (done) { 21 | var expected = [ 22 | // add files you expect to exist here. 23 | '.jshintrc', 24 | '.editorconfig' 25 | ]; 26 | 27 | helpers.mockPrompt(this.app, { 28 | 'someOption': true 29 | }); 30 | this.app.options['skip-install'] = true; 31 | this.app.run({}, function () { 32 | helpers.assertFile(expected); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/home.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Each module has a .module.js file. This file contains the angular module declaration - 3 | * angular.module("moduleName", []); 4 | * The build system ensures that all the *.module.js files get included prior to any other .js files, which 5 | * ensures that all module declarations occur before any module references. 6 | */ 7 | (function(module) { 8 | 9 | module.config(function ($stateProvider) { 10 | $stateProvider.state('home', { 11 | url: '/home', 12 | views: { 13 | "main": { 14 | controller: 'HomeController as model', 15 | templateUrl: 'home/home.tpl.html' 16 | } 17 | }, 18 | data:{ pageTitle: 'Home' } 19 | }); 20 | }); 21 | 22 | // The name of the module, followed by its dependencies (at the bottom to facilitate enclosure) 23 | }(angular.module("<%= projectName %>.home", [ 24 | 'ui.router' 25 | ]))); 26 | -------------------------------------------------------------------------------- /module/templates/_module.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | <% if (includeRest) { %> 3 | module.controller('<%= capitalModuleName %>Controller', function (<%= resourceName %>) { 4 | var model = this; 5 | model.loading = false; 6 | model.<%= camelModuleName %> = []; 7 | 8 | init(); 9 | 10 | function init() { 11 | model.<%= camelModuleName %> = get<%= capitalModuleName %>(); 12 | } 13 | 14 | function get<%= capitalModuleName %>() { 15 | model.loading = true; 16 | <%= resourceName %>.query().$promise.then(function(response) { 17 | model.<%= camelModuleName %> = response; 18 | model.loading = false; 19 | }); 20 | } 21 | }); 22 | <% } else { %> 23 | module.controller('<%= capitalModuleName %>Controller', function () { 24 | var model = this; 25 | 26 | init(); 27 | 28 | function init() { 29 | 30 | } 31 | }); 32 | <% } %> 33 | }(angular.module("<%= projectName %>.<%= camelModuleName %>"))); -------------------------------------------------------------------------------- /app/templates/root/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= projectName %>", 3 | "version": "0.0.1", 4 | "author": "<%= author %>", 5 | "test": "<%= projectName %>", 6 | "devDependencies": { 7 | "grunt": "~0.4.2", 8 | "grunt-coffeelint": "0.0.10", 9 | "grunt-contrib-clean": "^0.5.0", 10 | "grunt-contrib-coffee": "^0.10.1", 11 | "grunt-contrib-concat": "^0.3.0", 12 | "grunt-contrib-copy": "^0.5.0", 13 | "grunt-contrib-jshint": "^0.9.2", 14 | "grunt-contrib-less": "~0.11.0", 15 | "grunt-contrib-nodeunit": "~0.2.0", 16 | "grunt-contrib-uglify": "~0.2.7", 17 | "grunt-contrib-watch": "^0.6.1", 18 | "grunt-express": "^1.2.1", 19 | "grunt-html2js": "^0.2.4", 20 | "grunt-karma": "^0.8.2", 21 | "grunt-ng-annotate": "^0.3.2", 22 | "karma": "^0.12.31", 23 | "karma-chrome-launcher": "^0.1.3", 24 | "karma-coffee-preprocessor": "^0.2.1", 25 | "karma-firefox-launcher": "^0.1.3", 26 | "karma-jasmine": "^0.1.5", 27 | "karma-phantomjs-launcher": "^0.1.3", 28 | "lodash": "^2.4.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /module/templates/_moduleEdit.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | module.controller('Edit<%= resourceName %>Controller', function ($state, $window, <%= resourceName %>) { 4 | var model = this; 5 | model.<%= resourceInstance %> = {}; 6 | model.loading = false; 7 | model.update = update; 8 | 9 | init(); 10 | 11 | function init() { 12 | model.<%= resourceInstance %> = get<%= resourceName %>(); 13 | } 14 | 15 | function get<%= resourceName %>() { 16 | model.loading = true; 17 | <%= resourceName %>.get({ id: $state.params.id }).$promise 18 | .then(function(response) { 19 | model.<%= resourceInstance %> = response; 20 | model.loading = false; 21 | }); 22 | } 23 | 24 | function update() { 25 | model.<%= resourceInstance %>.$update() 26 | .then(function(response) { 27 | $window.location = '#/<%= lowerModuleName %>'; 28 | }); 29 | } 30 | }); 31 | 32 | }(angular.module("<%= projectName %>.<%= camelModuleName %>"))); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-ngbp", 3 | "version": "0.2.3", 4 | "description": "Yeoman generator based on the ngBoilerplate kickstarter, a best-practice boilerplate for scalable Angular projects built on a highly modular, folder-by-feature structure. Now with easily switchable mocking via $httpBackend and RESTful resource scaffolding.", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "angular", 8 | "angularjs", 9 | "boilerplate", 10 | "ngboilerplate", 11 | "scaffold", 12 | "enterprise", 13 | "framework", 14 | "module", 15 | "client", 16 | "grunt", 17 | "karma", 18 | "express", 19 | "app" 20 | ], 21 | "license": "MIT", 22 | "main": "app/index.js", 23 | "repository": "thardy/generator-ngbp", 24 | "author": { 25 | "name": "Tim Hardy", 26 | "email": "", 27 | "url": "https://github.com/thardy" 28 | }, 29 | "engines": { 30 | "node": ">=0.10.0" 31 | }, 32 | "scripts": { 33 | "test": "mocha" 34 | }, 35 | "dependencies": { 36 | "chalk": "~0.4.0", 37 | "inflected": "^1.1.6", 38 | "lodash": "^3.7.0", 39 | "touch": "0.0.3", 40 | "yeoman-generator": "~0.16.0", 41 | "yo": ">=1.0.0" 42 | }, 43 | "devDependencies": { 44 | "mocha": "*" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/templates/root/src/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%% styles.forEach( function ( file ) { %> 10 | <%% }); %> 11 | 12 | <%% scripts.forEach( function ( file ) { %> 13 | <%% }); %> 14 | 15 | 16 |
17 | 27 |
28 | 29 |
30 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/templates/root/src/less/main.less: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the main application stylesheet. It should include or import all 3 | * stylesheets used throughout the application as this is the only stylesheet in 4 | * the Grunt configuration that is automatically processed. 5 | */ 6 | 7 | 8 | /** 9 | * First, we include the Twitter Bootstrap LESS files. Only the ones used in the 10 | * project should be imported as the rest are just wasting space. 11 | */ 12 | 13 | @import '../../vendor/bootstrap/less/bootstrap.less'; 14 | 15 | /** 16 | * This is our main variables file. We must include it last so we can overwrite any variable 17 | * definitions in our imported stylesheets. 18 | */ 19 | 20 | @import 'variables.less'; 21 | 22 | 23 | /** 24 | * Typography 25 | */ 26 | 27 | @font-face { 28 | font-family: 'Roboto'; 29 | font-style: normal; 30 | font-weight: 400; 31 | src: local('Roboto Regular'), local('Roboto-Regular'), url(fonts/Roboto-Regular.woff) format('woff'); 32 | } 33 | 34 | 35 | /** 36 | * Now that all app-wide styles have been applied, we can load the styles for 37 | * all the submodules and components we are using. 38 | * 39 | * TODO: In a later version of this boilerplate, I'd like to automate this. 40 | */ 41 | 42 | @import '../app/home/home.less'; 43 | 44 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/_home.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | # Each section of the site has its own module. It probably also has 3 | # submodules, though this boilerplate is too simple to demonstrate it. Within 4 | # 'src/app/home', however, could exist several additional folders representing 5 | # additional modules that would then be listed as dependencies of this one. 6 | # For example, a 'note' section could have the submodules 'note.create', 7 | # 'note.delete', 'note.edit', etc. 8 | # 9 | # Regardless, so long as dependencies are managed correctly, the build process 10 | # will automatically take take of the rest. 11 | ### 12 | do (module=angular.module "<%= projectName %>.home") -> 13 | # As you add controllers to a module and they grow in size, feel free to 14 | # place them in their own files. Let each module grow organically, adding 15 | # appropriate organization and sub-folders as needed. 16 | module.controller 'HomeController', () -> 17 | model = this 18 | model.someVar = 'blue' 19 | model.someList = ['one', 'two', 'three'] 20 | model.someFunctionUsedByTheHomePage = -> 21 | alert('Congratulations') 22 | 23 | init = -> 24 | # A definitive place to put everything that needs to run when the 25 | # controller starts. Avoid writing any code outside of this function 26 | # that executes immediately. 27 | 28 | init() 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/templates/root/src/less/README.md: -------------------------------------------------------------------------------- 1 | # The `src/less` Directory 2 | 3 | This folder is actually fairly self-explanatory: it contains your LESS/CSS files to be compiled during the build. 4 | The only important thing to note is that *only* `main.less` will be processed during the build, meaning that all 5 | other stylesheets must be *imported* into that one. 6 | 7 | This should operate somewhat like the routing; the `main.less` file contains all of the site-wide styles, while 8 | any styles that are route-specific should be imported into here from LESS files kept alongside the JavaScript 9 | and HTML sources of that component. For example, the `home` section of the site has some custom styles, which 10 | are imported like so: 11 | 12 | ```css 13 | @import '../app/home/home.less'; 14 | ``` 15 | 16 | The same principal, though not demonstrated in the code, would also apply to reusable components. CSS or LESS 17 | files from external components would also be imported. If, for example, we had a Twitter feed directive with 18 | an accompanying template and style, we would similarly import it: 19 | 20 | ```css 21 | @import '../common/twitterFeed/twitterFeedDirective.less'; 22 | ``` 23 | 24 | Using this decentralized approach for all our code (JavaScript, HTML, and CSS) creates a framework where a 25 | component's directory can be dragged and dropped into *any other project* and it will "just work". 26 | 27 | I would like to eventually automate the importing during the build so that manually importing it here would no 28 | longer be required, but more thought must be put in to whether this is the best approach. 29 | -------------------------------------------------------------------------------- /app/templates/root/src/_README.md: -------------------------------------------------------------------------------- 1 | # The `src` Directory 2 | 3 | ## Overview 4 | 5 | The `src/` directory contains all code used in the application along with all 6 | tests of such code. 7 | 8 | ``` 9 | src/ 10 | |- app/ 11 | | |- about/ 12 | | |- home/ 13 | | |- app.js 14 | | |- app.spec.js 15 | |- assets/ 16 | |- common/ 17 | |- less/ 18 | | |- main.less 19 | | |- variables.less 20 | |- index.html 21 | ``` 22 | 23 | - `src/app/` - application-specific code, i.e. code not likely to be reused in 24 | another application. [Read more »](app/README.md) 25 | - `src/assets/` - static files like fonts and images. 26 | [Read more »](assets/README.md) 27 | - `src/common/` - third-party libraries or components likely to be reused in 28 | another application. [Read more »](common/README.md) 29 | - `src/less/` - LESS CSS files. [Read more »](less/README.md) 30 | - `src/index.html` - this is the HTML document of the single-page application. 31 | See below. 32 | 33 | See each directory for a detailed explanation. 34 | 35 | ## `index.html` 36 | 37 | The `index.html` file is the HTML document of the single-page application (SPA) 38 | that should contain all markup that applies to everything in the app, such as 39 | the header and footer. It declares with `ngApp` that this is `<%= projectName %>`, 40 | specifies the main `AppController` controller, and contains the `ngView` directive 41 | into which route templates are placed. 42 | 43 | Unlike any other HTML document (e.g. the templates), `index.html` is compiled as 44 | a Grunt template, so variables from `Gruntfile.js` and `package.json` can be 45 | referenced from within it. Changing `name` in `package.json` from 46 | "<%= projectName %>" will rename the resultant CSS and JavaScript placed in `build/`, 47 | so this HTML references them by variable for convenience. 48 | -------------------------------------------------------------------------------- /app/templates/root/src/app/home/_home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Each section of the site has its own module. It probably also has 3 | * submodules, though this boilerplate is too simple to demonstrate it. Within 4 | * 'src/app/home', however, could exist several additional folders representing 5 | * additional modules that would then be listed as dependencies of this one. 6 | * For example, a 'note' section could have the submodules 'note.create', 7 | * 'note.delete', 'note.edit', etc. 8 | * 9 | * Regardless, so long as dependencies are managed correctly, the build process 10 | * will automatically take take of the rest. 11 | */ 12 | (function(module) { 13 | 14 | // As you add controllers to a module and they grow in size, feel free to place them in their own files. 15 | // Let each module grow organically, adding appropriate organization and sub-folders as needed. 16 | module.controller('HomeController', function () { 17 | // The top section of a controller should be lean and make it easy to see the "signature" of the controller 18 | // at a glance. All function definitions should be contained lower down. 19 | var model = this; 20 | model.someVar = 'blue'; 21 | model.someList = ['one', 'two', 'three']; 22 | model.someFunctionUsedByTheHomePage = someFunctionUsedByTheHomePage; 23 | 24 | init(); 25 | 26 | function init() { 27 | // A definitive place to put everything that needs to run when the controller starts. Avoid 28 | // writing any code outside of this function that executes immediately. 29 | } 30 | 31 | function someFunctionUsedByTheHomePage() { 32 | alert('Congratulations'); 33 | } 34 | 35 | }); 36 | 37 | // The name of the module, followed by its dependencies (at the bottom to facilitate enclosure) 38 | }(angular.module("<%= projectName %>.home"))); -------------------------------------------------------------------------------- /module/templates/_module.module.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | <% if (includeRest) { %> 3 | module.config(function ($stateProvider) { 4 | $stateProvider 5 | .state('<%= routeFriendlyName %>', { 6 | url: '/<%= path %>', 7 | views: { 8 | 'main@': { 9 | controller: '<%= capitalModuleName %>Controller as model', 10 | templateUrl: '<%= path %>/<%= filePrefix %>.tpl.html' 11 | } 12 | }, 13 | data:{ pageTitle: '<%= capitalModuleName %>' } 14 | }) 15 | .state('add<%= resourceName %>', { 16 | url: '/<%= path %>/add-<%= singularKebabModuleName %>', 17 | views: { 18 | 'main@': { 19 | controller: 'Add<%= resourceName %>Controller as model', 20 | templateUrl: '<%= path %>/add<%= resourceName %>.tpl.html' 21 | } 22 | }, 23 | data:{ pageTitle: 'Add <%= resourceName %>' } 24 | }) 25 | .state('edit<%= resourceName %>', { 26 | url: '/<%= path %>/edit-<%= singularKebabModuleName %>/{id}', 27 | views: { 28 | 'main@': { 29 | controller: 'Edit<%= resourceName %>Controller as model', 30 | templateUrl: '<%= path %>/edit<%= resourceName %>.tpl.html' 31 | } 32 | }, 33 | data:{ pageTitle: 'Edit <%= resourceName %>' } 34 | }); 35 | }); 36 | <% } else { %> 37 | module.config(function ($stateProvider) { 38 | $stateProvider.state('<%= routeFriendlyName %>', { 39 | url: '/<%= kebabModuleName %>', 40 | views: { 41 | "main": { 42 | controller: '<%= capitalModuleName %>Controller as model', 43 | templateUrl: '<%= path %>/<%= kebabModuleName %>.tpl.html' 44 | } 45 | }, 46 | data:{ pageTitle: '<%= capitalModuleName %>' } 47 | }); 48 | }); 49 | <% } %> 50 | }(angular.module('<%= projectName %>.<%= camelModuleName %>', [ 51 | 'ui.router'<% if (includeRest) { %>, 52 | 'ngResource'<% } %> 53 | ]))); 54 | -------------------------------------------------------------------------------- /app/templates/root/karma/karma-unit.tpl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( karma ) { 2 | karma.set({ 3 | /** 4 | * From where to look for files, starting with the location of this file. 5 | */ 6 | basePath: '../', 7 | 8 | /** 9 | * This is the list of file patterns to load into the browser during testing. 10 | */ 11 | files: [ 12 | <% scripts.forEach( function ( file ) { %>'<%= file %>', 13 | <% }); %> 14 | 'src/**/*.module.js', 15 | 'src/**/*.js', 16 | 'src/**/*.module.coffee', 17 | 'src/**/*.coffee', 18 | ], 19 | exclude: [ 20 | 'src/assets/**/*.js' 21 | ], 22 | frameworks: [ 'jasmine' ], 23 | plugins: [ 'karma-jasmine', 'karma-firefox-launcher', 'karma-chrome-launcher', 'karma-phantomjs-launcher', 'karma-coffee-preprocessor' ], 24 | preprocessors: { 25 | '**/*.coffee': 'coffee', 26 | }, 27 | 28 | /** 29 | * How to report, by default. 30 | */ 31 | reporters: 'dots', 32 | 33 | /** 34 | * On which port should the browser connect, on which port is the test runner 35 | * operating, and what is the URL path for the browser to use. 36 | */ 37 | port: 9018, 38 | runnerPort: 9100, 39 | urlRoot: '/', 40 | 41 | /** 42 | * Disable file watching by default. 43 | */ 44 | autoWatch: false, 45 | 46 | /** 47 | * The list of browsers to launch to test on. This includes only "Firefox" by 48 | * default, but other browser names include: 49 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 50 | * 51 | * Note that you can also use the executable name of the browser, like "chromium" 52 | * or "firefox", but that these vary based on your operating system. 53 | * 54 | * You may also leave this blank and manually navigate your browser to 55 | * http://localhost:9018/ when you're running tests. The window/tab can be left 56 | * open and the tests will automatically occur there during the build. This has 57 | * the aesthetic advantage of not launching a browser every time you save. 58 | */ 59 | browsers: [ 60 | 'Firefox' 61 | ] 62 | }); 63 | }; 64 | 65 | -------------------------------------------------------------------------------- /app/templates/root/src/app/README.md: -------------------------------------------------------------------------------- 1 | # The `src/app` Directory 2 | 3 | ## Overview 4 | 5 | ``` 6 | src/ 7 | |- app/ 8 | | |- home/ 9 | | |- about/ 10 | | |- app.js 11 | | |- app.spec.js 12 | ``` 13 | 14 | The `src/app` directory contains all code specific to this application. Apart 15 | from `app.js` and its accompanying tests (discussed below), this directory is 16 | filled with subdirectories corresponding to high-level sections of the 17 | application, often corresponding to top-level routes. Each directory can have as 18 | many subdirectories as it needs, and the build system will understand what to 19 | do. For example, a top-level route might be "products", which would be a folder 20 | within the `src/app` directory that conceptually corresponds to the top-level 21 | route `/products`, though this is in no way enforced. Products may then have 22 | subdirectories for "create", "view", "search", etc. The "view" submodule may 23 | then define a route of `/products/:id`, ad infinitum. 24 | 25 | As `ngBoilerplate` is quite minimal, take a look at the two provided submodules 26 | to gain a better understanding of how these are used as well as to get a 27 | glimpse of how powerful this simple construct can be. 28 | 29 | ## `app.js` 30 | 31 | This is our main app configuration file. It kickstarts the whole process by 32 | requiring all the modules from `src/app` that we need. We must load these now to 33 | ensure the routes are loaded. If as in our "products" example there are 34 | subroutes, we only require the top-level module, and allow the submodules to 35 | require their own submodules. 36 | 37 | As a matter of course, we also require the template modules that are generated 38 | during the build. 39 | 40 | However, the modules from `src/common` should be required by the app 41 | submodules that need them to ensure proper dependency handling. These are 42 | app-wide dependencies that are required to assemble your app. 43 | 44 | ```js 45 | angular.module( '<%= projectName %>', [ 46 | 'templates-app', 47 | 'templates-common', 48 | '<%= projectName %>.home', 49 | '<%= projectName %>.about' 50 | 'ui.state', 51 | 'ui.route' 52 | ]) 53 | ``` 54 | 55 | With app modules broken down in this way, all routing is performed by the 56 | submodules we include, as that is where our app's functionality is really 57 | defined. So all we need to do in `app.js` is specify a default route to follow, 58 | which route of course is defined in a submodule. In this case, our `home` module 59 | is where we want to start, which has a defined route for `/home` in 60 | `src/app/home/home.js`. 61 | 62 | ```js 63 | .config( function myAppConfig ( $stateProvider, $urlRouterProvider ) { 64 | $urlRouterProvider.otherwise( '/home' ); 65 | }) 66 | ``` 67 | 68 | Use the main applications run method to execute any code after services 69 | have been instantiated. 70 | 71 | ```js 72 | .run( function () { 73 | }) 74 | ``` 75 | 76 | And then we define our main application controller. This is a good place for logic 77 | not specific to the template or route, such as menu logic or page title wiring. 78 | 79 | ```js 80 | .controller( 'AppCtrl', function AppCtrl ( $scope, $location ) { 81 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ 82 | if ( angular.isDefined( toState.data.pageTitle ) ) { 83 | $scope.pageTitle = toState.data.pageTitle + ' | <%= projectName %>' ; 84 | } 85 | }); 86 | }) 87 | ``` 88 | 89 | ### Testing 90 | 91 | One of the design philosophies of `ngBoilerplate` is that tests should exist 92 | alongside the code they test and that the build system should be smart enough to 93 | know the difference and react accordingly. As such, the unit test for `app.js` 94 | is `app.spec.js`, though it is quite minimal. 95 | -------------------------------------------------------------------------------- /app/templates/root/src/app/_mockApp.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | module.run(function($httpBackend, $filter, $location) { 3 | 4 | // Intercept only the calls we want to mock 5 | 6 | // EXAMPLE CRUD INTERCEPTION OF A RESOURCE NAMED "Products". 7 | // Feel free to refactor this out into other files, but heed the following advice... 8 | // I wouldn't build automated tests on this or even try to maintain this for all features going forward because 9 | // it can become a nightmare. Once you get a feature working, I recommend simply re-purposing/overwriting this 10 | // code for the next feature. I've come to believe it's best used for rapid prototyping one feature at a time, 11 | // not for maintaining for all features. 12 | 13 | // *** Products ***************************************************************************************** 14 | // I use objects because writing json in javascript is a pain and converting via $filter is easy. 15 | // This array will act as our fake database. 16 | var products = [ 17 | { id: 1, name: 'Doohicky', description: 'It is what it is.' }, 18 | { id: 2, name: 'Thingamajig', description: 'One of those things...' }, 19 | { id: 3, name: 'Widget', description: 'A smaller version of the thingamajig.' } 20 | ]; 21 | 22 | var indentSpacing = 2; 23 | 24 | // Products GET with id - these tend to be greedy so the more specific GET with parm HAS to be listed 25 | // before the generic GET all or the GET all will suck up the GET with parm requests. 26 | $httpBackend.whenGET(/api\/products\/[\d]+/).respond(function(method, url, data) { 27 | var path = $location.path(); // returns something like "/products/edit-product/1", so the id will be after the third slash 28 | var pathArray = path.split('/'); 29 | var productId = pathArray[3]; 30 | return [200, $filter('json')(getById(products, productId), indentSpacing), { 'Cache-control': 'no-cache' }]; 31 | }); 32 | 33 | // Products GET all 34 | $httpBackend.whenGET(/api\/products/).respond(function(method, url, data) { 35 | return [200, $filter('json')(products, indentSpacing), { 'Cache-control': 'no-cache' }]; 36 | }); 37 | 38 | // Products POST 39 | $httpBackend.whenPOST(/api\/products/).respond(function(method, url, data) { 40 | // Add to our products array, which is acting like a fake database 41 | var newProduct = angular.fromJson(data); 42 | newProduct.id = products.push(newProduct); // data should be the json product that was posted to us, 43 | // and the new length of products should make a fine fake Id 44 | return [200, newProduct, {}]; // respond expects us to return array containing status, data, headers 45 | }); 46 | 47 | // Products PUT 48 | $httpBackend.whenPUT(/api\/products\/[\d]+/).respond(function(method, url, data) { 49 | // Update our products array, which is acting like a fake database 50 | var updatedProduct = angular.fromJson(data); 51 | var index = products.indexOf(getById(products, updatedProduct.id)); 52 | products[index] = updatedProduct; 53 | return [201, updatedProduct, {}]; // respond expects us to return array containing status, data, headers 54 | }); 55 | // *************************************************************************************************** 56 | 57 | 58 | // Now let everything else, everything not specified above, pass through to their real http calls 59 | $httpBackend.whenGET(/.+/).passThrough(); 60 | $httpBackend.whenPOST(/.+/).passThrough(); 61 | $httpBackend.whenPUT(/.+/).passThrough(); 62 | 63 | 64 | // Helper functions 65 | function getById(array, id) { 66 | var len = array.length; 67 | for (i = 0; i < len; i++) { 68 | if (+array[i].id == +id) { 69 | return array[i]; 70 | } 71 | } 72 | return null; 73 | } 74 | }); 75 | 76 | // Your normal app is listed as a dependency, so all your functionality is there. We've basically just extended it 77 | // with this http interception code. 78 | })(angular.module('mockApp', ['<%=projectName%>', 'ngMockE2E'])); 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-ngbp 2 | 3 | > Yeoman Generator based on the popular ngBoilerplate AngularJS kickstarter. ngBoilerplate is a best-practice boilerplate for scalable Angular projects built on a highly modular, folder-by-feature structure. You work in vertical slices on a daily basis (view, controller, service, etc), so why not organize your projects to optimize your workflow, maximize discoverability, and get copy-paste module reuse for free? 4 | 5 | ## Latest Updates 6 | (4/22/15) Added some pretty hefty improvements - easily switchable mocking with $httpBackend and scaffolding of RESTful resources. See below. Also updated to Angular 1.3.15 and hardcoded the bower version to avoid issues. As always, please let me know of any issues. 7 | 8 | (12/29/14) Many best-practice improvements, including use of "Controller as model" syntax, cleaner controllers, plus separate \*.module.js file for module declarations and 9 | grunt build support for those files. 10 | Coffeescript is close but broken with the *.module.coffee being included twice in resultant js file. I could use some help from anyone who works 11 | in coffeescript regularly, especially coffeescript grunt builds. 12 | 13 | ## Quick Start 14 | 15 | Install generator-ngbp from npm, run: 16 | 17 | ``` 18 | $ npm install -g generator-ngbp 19 | ``` 20 | 21 | Create a new directory for your project and cd into it: 22 | 23 | ``` 24 | $ mkdir my-new-project 25 | $ cd my-new-project 26 | ``` 27 | 28 | Initiate the generator: 29 | 30 | ``` 31 | $ yo ngbp 32 | ``` 33 | ### Mocking 34 | You can now switch in and out of mocking mode by simply running ```grunt watchmock``` vs ```grunt watch```. Switching between the two will handle all the necessary configuration within your SPA. Running in mock mode will use $httpBackend to intercept any external http calls you manually configure and return whatever results you want. 35 | 36 | See mockApp.js for a full CRUD example (*actually, Delete isn't in yet*), and combine with the new RESTful module generator for a full, working, end-to-end example - great for prototyping data-driven UX with fully functional controllers and api-calling angular services without having to actually build the api first. Simply switch back to ```grunt watch``` and your code will now be making real external calls to a real (if it exists) api. This is my preferred mocking implementation because your code is written to actually make the http call, and the interception is handled by angular. When you are ready to hit a real api, you have no code changes to make. 37 | 38 | ### Sub-Generators 39 | 40 | There's only one subgenerator at the moment 41 | ngbp:module 42 | 43 | To create a new module... 44 | 45 | ``` 46 | $ yo ngbp:module "moduleName" 47 | ``` 48 | 49 | You can specify the root folder of the module via prompt - default is "app". 50 | 51 | If you specify a module name with a dot (e.g. account.profile), you will be prompted to confirm that you want to create a sub module. Choosing y (default), a new 'profile' module will be created in account folder. 52 | 53 | You will be prompted whether you want your module to wrap a RESTful resource. The default is no. 54 | 55 | You have to authorize the overwrite of app.js when the subgenerator adds a dependency for your new module (the default is Y, so you can just hit enter at the prompt). 56 | 57 | ##### RESTful Scaffolding 58 | If you choose yes to wrap a RESTful resource, you will get fully scaffolded RESTful controllers, views, and a service for CRUD support, using ngResource under the covers - so don't forget to answer 'y' when asked if you want angular-resource when you generate your app, or just add it yourself. The naming convention is currently setup to handle straightforward pluralized module names, like "products", and the scaffolding assumes an api hanging off whatever domain your spa is running on (e.g. http://mydomain.com/api/products). Try the following... 59 | ``` 60 | yo ngbp:module "products" 61 | ``` 62 | ...type ```y``` when asked if you want to wrap a RESTful resource, then switch your ```grunt watch``` over to ```grunt watchmock``` to see a fully-working example. Navigate to http://localhost:9000/#/products to view (there's no navigation scaffolding at this point, but the routing is there). The mocked results for "products" are handled in a heavily-commented mockApp.js, which you can edit to mock whatever you want. 63 | 64 | There's also still a bug with grunt watch that doesn't always see new files in new folders - https://github.com/gruntjs/grunt-contrib-watch/issues/70. Stopping and 65 | re-running grunt watch will always work though. 66 | 67 | ### ngBoilerplate Tips 68 | 69 | When adding bower modules, always install with 70 | ``` 71 | $ bower install some-bower-module --save-dev 72 | ``` 73 | Then manually edit the vendor_files.js variable in Gruntfile.js to add the full path to the js files you need from the vendor folder. 74 | This grunt variable is what is used to create the script tags in the header of your index.html in the build folder (dev site). 75 | When you run "grunt compile", this same variable is used to add the vendor files to the single, minified js file in the bin folder (prod site). 76 | 77 | ### More Info 78 | 79 | To learn more about ngBoilerplate, [click here](https://github.com/ngbp/ngbp) 80 | 81 | 82 | 83 | ## License 84 | 85 | MIT 86 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var path = require('path'); 4 | var yeoman = require('yeoman-generator'); 5 | var chalk = require('chalk'); 6 | 7 | 8 | var NgbpGenerator = yeoman.generators.Base.extend({ 9 | init: function () { 10 | this.pkg = require('../package.json'); 11 | 12 | this.on('end', function () { 13 | if (!this.options['skip-install']) { 14 | this.installDependencies({ 15 | callback: function () { 16 | // Emit a new event - dependencies installed 17 | this.emit('dependenciesInstalled'); 18 | }.bind(this) 19 | }); 20 | } 21 | }); 22 | 23 | // Now we can bind to the dependencies installed event 24 | this.on('dependenciesInstalled', function() { 25 | //this.spawnCommand('grunt', ['build']); 26 | this.log(chalk.green( 27 | '\nYou\'re good to go!!!!\n' + 28 | 'Simply running ') + chalk.cyan.bold("grunt watch") + chalk.green(' will do the following:\n' + 29 | ' - Build everything (concat, create js templates of html, etc) and place it into a "build" folder\n' + 30 | ' - Run all your tests\n' + 31 | ' - Watch your files for changes to do the above without any intervention\n' + 32 | ' - Launch express server to host your app at http://localhost:9000/index.html\n' + 33 | ' - Setup LiveReload so you immediately see changes in your browser (you still have to enable LiveReload on your browser)\n')); 34 | }); 35 | }, 36 | 37 | askFor: function () { 38 | var done = this.async(); 39 | 40 | // have Yeoman greet the user 41 | this.log(this.yeoman); 42 | 43 | this.log(chalk.magenta('You\'re using the ngbp (AngularBoilerplate) generator, a best-practice boilerplate\n for any scale Angular project built on a highly modular, folder-by-feature structure.')); 44 | 45 | var prompts = [ 46 | { 47 | name: 'projectName', 48 | message: 'What do you want to name your project?\n e.g. angular.module(\'exactlyWhatYouTypeBelow\', [])\n ?', 49 | default: 'myProject' 50 | }, 51 | { 52 | name: 'author', 53 | message: 'What is the author or company name?\n Used for copyright\'s in html, banners in code, and author prop in package.json\n ?', 54 | default: 'Somebody Special' 55 | }, 56 | { 57 | type: 'confirm', 58 | name: 'useCoffeescript', 59 | message: 'Would you like to use Coffeescript?', 60 | default: false 61 | }, 62 | { 63 | type: 'confirm', 64 | name: 'includeAngularResource', 65 | message: 'Do you want to include angular-resource, helpful for calling RESTful apis?', 66 | default: true 67 | }, 68 | ]; 69 | 70 | this.prompt(prompts, function (props) { 71 | this.projectName = props.projectName; 72 | this.author = props.author; 73 | this.useCoffeescript = props.useCoffeescript; 74 | this.includeAngularResource = props.includeAngularResource; 75 | 76 | done(); 77 | }.bind(this)); 78 | }, 79 | 80 | config: function() { 81 | this.config.set('projectName', this.projectName); 82 | this.config.set('useCoffeescript', this.useCoffeescript); 83 | this.config.save(); 84 | }, 85 | 86 | _processDirectory: function (source, destination) { 87 | var root = this.isPathAbsolute(source) ? source : path.join(this.sourceRoot(), source); 88 | var files = this.expandFiles('**', { dot: true, cwd: root }); 89 | var useCoffeescript = this.config.get('useCoffeescript'); 90 | 91 | for (var i = 0; i < files.length; i++) { 92 | var f = files[i]; 93 | var fExt = f.split('.').pop().toLowerCase(); 94 | var fIsSource = path.dirname(f).split('/').shift() == 'src'; 95 | var isExcluded = false; 96 | if (fIsSource) { 97 | if ((useCoffeescript && fExt == 'js') || (!useCoffeescript && fExt == 'coffee')) {isExcluded = true;} 98 | } 99 | var src = path.join(root, f); 100 | if (!isExcluded) { 101 | if (path.basename(f).indexOf('_') == 0) { 102 | var dest = path.join(destination, path.dirname(f), path.basename(f).replace(/^_/, '')); 103 | this.template(src, dest); 104 | } 105 | else { 106 | var dest = path.join(destination, f); 107 | this.copy(src, dest); 108 | } 109 | } 110 | } 111 | }, 112 | 113 | app: function () { 114 | this._processDirectory('root', ''); 115 | // this.mkdir('app'); 116 | // this.mkdir('app/templates'); 117 | // 118 | // this.copy('_package.json', 'package.json'); 119 | // this.copy('_bower.json', 'bower.json'); 120 | } 121 | 122 | }); 123 | 124 | module.exports = NgbpGenerator; -------------------------------------------------------------------------------- /module/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var path = require('path'); 4 | var touch = require('touch'); 5 | var yeoman = require('yeoman-generator'); 6 | var inflector = require('inflected'); 7 | var _ = require('lodash'); 8 | 9 | 10 | var ModuleGenerator = yeoman.generators.NamedBase.extend({ 11 | init: function () { 12 | console.log('Creating the module - ' + this.name); 13 | }, 14 | 15 | askFor: function () { 16 | var done = this.async(); 17 | var prompts = []; 18 | // confirm with user if this module is a sub module (dot notation) 19 | if (this.name.indexOf('.') !== -1) { 20 | var parent = this.name.substr(0, this.name.lastIndexOf('.')); 21 | prompts.push({ 22 | name: 'isSubmodule', 23 | message: 'Looks like ' + this.name + ' is a submodule, do you want to create it in ' + parent + ' module?', 24 | default: 'y' 25 | }); 26 | } 27 | prompts.push({ 28 | name: 'rootFolder', 29 | message: 'Where do you want to place this module - what is the root folder?', 30 | default: 'app' 31 | }); 32 | 33 | prompts.push({ 34 | type: 'confirm', 35 | name: 'includeRest', 36 | message: "Do you want this module to wrap a REST-ful resource? (we'll include a service to call an Api, basic controllers, and views)", 37 | default: false 38 | }); 39 | 40 | this.prompt(prompts, function (props) { 41 | this.rootFolder = props.rootFolder; 42 | this.isSubmodule = props.isSubmodule == 'y'; 43 | this.includeRest = props.includeRest; 44 | 45 | done(); 46 | }.bind(this)); 47 | }, 48 | 49 | files: function () { 50 | var modulePath; 51 | this.projectName = this.config.get('projectName'); 52 | var self = this; 53 | var capitalModuleName = []; 54 | // controller name cannot have a dot or dash in it and must be unique in the app 55 | this.name.split(/[\.-]/).forEach(function (value) { 56 | capitalModuleName.push(self._.capitalize(value)); 57 | }); 58 | 59 | this.capitalModuleName = capitalModuleName.join(''); 60 | this.routeFriendlyName = this.name.replace('.', '-'); 61 | this.camelModuleName = _.camelCase(this.capitalModuleName, false); 62 | this.resourceInstance = inflector.singularize(this.camelModuleName); 63 | this.resourceName = this._.capitalize(this.resourceInstance); 64 | this.kebabModuleName = _.kebabCase(this.name); 65 | this.singularKebabModuleName = _.kebabCase(inflector.singularize(this.name)); 66 | 67 | if (this.isSubmodule) { 68 | this.lowerModuleName = this.name.toLowerCase().replace('.', '/'); 69 | this.filePrefix = this.name.substr(this.name.lastIndexOf('.') + 1); 70 | this.path = this.name.replace(/\./g, '/') 71 | modulePath = path.join('src', this.rootFolder, this.path); 72 | 73 | this.resourceInstance = inflector.singularize(_.camelCase(this._.capitalize(this.filePrefix), false)); 74 | this.resourceName = this._.capitalize(this.resourceInstance); 75 | this.singularKebabModuleName = _.kebabCase(inflector.singularize(this.filePrefix)); 76 | 77 | } else { 78 | this.lowerModuleName = this.name.toLowerCase(); 79 | this.filePrefix = this.name; 80 | this.path = this.name; 81 | modulePath = path.join('src', this.rootFolder, this.name); 82 | } 83 | this.mkdir(modulePath); 84 | 85 | if (this.config.get('useCoffeescript')) { 86 | this.template('_module.module.coffee', path.join(modulePath, this.filePrefix + '.module.coffee')); 87 | this.template('_module.coffee', path.join(modulePath, this.filePrefix + '.coffee')); 88 | this.template('_moduleSpec.coffee', path.join(modulePath, this.filePrefix + '.spec.coffee')); 89 | 90 | if (this.includeRest) { 91 | // if you're reading this, you should probably add the coffee files for the REST implementation 92 | } 93 | } else { 94 | this.template('_module.module.js', path.join(modulePath, this.filePrefix + '.module.js')); 95 | this.template('_module.js', path.join(modulePath, this.filePrefix + '.js')); 96 | this.template('_moduleSpec.js', path.join(modulePath, this.filePrefix + '.spec.js')); 97 | 98 | 99 | if (this.includeRest) { 100 | this.template('_moduleAdd.js', path.join(modulePath, 'add' + this.resourceName + '.js')); 101 | this.template('_moduleAdd.tpl.html', path.join(modulePath, 'add' + this.resourceName + '.tpl.html')); 102 | this.template('_moduleEdit.js', path.join(modulePath, 'edit' + this.resourceName + '.js')); 103 | this.template('_moduleEdit.tpl.html', path.join(modulePath, 'edit' + this.resourceName + '.tpl.html')); 104 | this.template('_moduleService.js', path.join(modulePath, this.resourceInstance + 'Service.js')); 105 | 106 | var directivesDir = path.join(modulePath, 'directives'); 107 | this.mkdir(directivesDir); 108 | this.template(path.join('directives', '_moduleForm.js'), path.join(directivesDir, this.resourceInstance + 'Form.js')); 109 | this.template(path.join('directives', '_moduleForm.tpl.html'), path.join(directivesDir, this.resourceInstance + 'Form.tpl.html')); 110 | } 111 | } 112 | this.template('_moduleList.tpl.html', path.join(modulePath, this.filePrefix + '.tpl.html')); 113 | this.template('_module.less', path.join(modulePath, this.filePrefix + '.less')); 114 | 115 | this._addModuleToAppJs(this.projectName, this.camelModuleName, this.lowerModuleName); 116 | 117 | // if (this.includeRestfulService) { 118 | // // Add RESTful service stuff here 119 | // } 120 | }, 121 | 122 | touchIndexHtml: function () { 123 | // Touch the index.html file to force the index grunt task to rebuild it (that task adds the new module to the scripts) 124 | var indexHtmlFilePath = 'src/index.html'; 125 | touch(indexHtmlFilePath, { 126 | mtime: true 127 | }); 128 | }, 129 | 130 | _addModuleToAppJs: function app(projectName, camelModuleName, lowerModuleName) { 131 | var hook = '])));', 132 | path = 'src/app/app.js', 133 | insert = " '" + projectName + "." + camelModuleName + "',\n"; 134 | 135 | if (this.config.get('useCoffeescript')) { 136 | hook = "'templates-app',"; 137 | path = 'src/app/app.coffee'; 138 | insert = "'" + projectName + "." + camelModuleName + "',\n "; 139 | } 140 | 141 | var file = this.readFileAsString(path); 142 | 143 | if (file.indexOf(insert) === -1) { 144 | this.write(path, file.replace(hook, insert + hook)); 145 | } 146 | } 147 | 148 | }); 149 | 150 | module.exports = ModuleGenerator; 151 | -------------------------------------------------------------------------------- /app/templates/root/_Gruntfile.js: -------------------------------------------------------------------------------- 1 | var taskName = ''; 2 | module.exports = function(grunt) { 3 | 4 | var _ = require('lodash'); 5 | 6 | // Load required Grunt tasks. These are installed based on the versions listed 7 | // * in 'package.json' when you do 'npm install' in this directory. 8 | grunt.loadNpmTasks('grunt-contrib-clean'); 9 | grunt.loadNpmTasks('grunt-contrib-copy'); 10 | grunt.loadNpmTasks('grunt-contrib-jshint'); 11 | grunt.loadNpmTasks('grunt-contrib-concat'); 12 | grunt.loadNpmTasks('grunt-contrib-watch'); 13 | grunt.loadNpmTasks('grunt-contrib-uglify'); 14 | grunt.loadNpmTasks('grunt-contrib-coffee'); 15 | grunt.loadNpmTasks('grunt-contrib-less'); 16 | grunt.loadNpmTasks('grunt-coffeelint'); 17 | grunt.loadNpmTasks('grunt-karma'); 18 | grunt.loadNpmTasks('grunt-ng-annotate'); 19 | grunt.loadNpmTasks('grunt-html2js'); 20 | grunt.loadNpmTasks('grunt-express'); 21 | 22 | /** ********************************************************************************* */ 23 | /** **************************** File Config **************************************** */ 24 | var fileConfig = { 25 | build_dir: 'build', 26 | compile_dir: 'bin', 27 | 28 | /** 29 | * This is a collection of file patterns for our app code (the 30 | * stuff in 'src/'). These paths are used in the configuration of 31 | * build tasks. 'js' is all project javascript, except tests. 32 | * 'commonTemplates' contains our reusable components' ('src/common') 33 | * template HTML files, while 'appTemplates' contains the templates for 34 | * our app's code. 'html' is just our main HTML file. 'less' is our main 35 | * stylesheet, and 'unit' contains our app's unit tests. 36 | */ 37 | app_files: { 38 | js: [ './src/**/*.module.js', 'src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ], 39 | jsunit: [ 'src/**/*.spec.js' ], 40 | 41 | coffee: [ './src/**/*.module.coffee', 'src/**/*.coffee', '!src/**/*.spec.coffee' ], 42 | coffeeunit: [ 'src/**/*.spec.coffee' ], 43 | 44 | appTemplates: [ 'src/app/**/*.tpl.html' ], 45 | commonTemplates: [ 'src/common/**/*.tpl.html' ], 46 | 47 | html: [ 'src/index.html' ], 48 | less: 'src/less/main.less' 49 | }, 50 | 51 | /** 52 | * This is a collection of files used during testing only. 53 | */ 54 | test_files: { 55 | js: [ 56 | 'vendor/angular-mocks/angular-mocks.js' 57 | ] 58 | }, 59 | 60 | /** 61 | * This is the same as 'app_files', except it contains patterns that 62 | * reference vendor code ('vendor/') that we need to place into the build 63 | * process somewhere. While the 'app_files' property ensures all 64 | * standardized files are collected for compilation, it is the user's job 65 | * to ensure non-standardized (i.e. vendor-related) files are handled 66 | * appropriately in 'vendor_files.js'. 67 | * 68 | * The 'vendor_files.js' property holds files to be automatically 69 | * concatenated and minified with our project source files. 70 | * 71 | * The 'vendor_files.css' property holds any CSS files to be automatically 72 | * included in our app. 73 | * 74 | * The 'vendor_files.assets' property holds any assets to be copied along 75 | * with our app's assets. This structure is flattened, so it is not 76 | * recommended that you use wildcards. 77 | */ 78 | vendor_files: { 79 | js: [ 80 | 'vendor/angular/angular.js', 81 | <% if (includeAngularResource) {%>'vendor/angular-resource/angular-resource.js',<% } %> 82 | 'vendor/angular-bootstrap/ui-bootstrap-tpls.min.js', 83 | 'vendor/placeholders/angular-placeholders-0.0.1-SNAPSHOT.min.js', 84 | 'vendor/angular-ui-router/release/angular-ui-router.js', 85 | 'vendor/angular-ui-utils/modules/route/route.js' 86 | ], 87 | css: [ 88 | ], 89 | assets: [ 90 | ] 91 | } 92 | }; 93 | 94 | /** ********************************************************************************* */ 95 | /** **************************** Task Config **************************************** */ 96 | var taskConfig = { 97 | pkg: grunt.file.readJSON("package.json"), 98 | 99 | /** 100 | * The banner is the comment that is placed at the top of our compiled 101 | * source files. It is first processed as a Grunt template, where the 'less than percent equals' 102 | * pairs are evaluated based on this very configuration object. 103 | */ 104 | meta: { 105 | banner: 106 | '/**\n' + 107 | ' * <%%= pkg.name %> - v<%%= pkg.version %> - <%%= grunt.template.today("yyyy-mm-dd") %>\n' + 108 | ' *\n' + 109 | ' * Copyright (c) <%%= grunt.template.today("yyyy") %> <%%= pkg.author %>\n' + 110 | ' */\n' 111 | }, 112 | 113 | /** 114 | * The directories to delete when 'grunt clean' is executed. 115 | */ 116 | clean: { 117 | all: [ 118 | '<%%= build_dir %>', 119 | '<%%= compile_dir %>' 120 | ], 121 | vendor: [ 122 | '<%%= build_dir %>/vendor/' 123 | ], 124 | index: [ '<%%= build_dir %>/index.html' ] 125 | }, 126 | 127 | /** 128 | * The 'copy' task just copies files from A to B. We use it here to copy 129 | * our project assets (images, fonts, etc.) and javascripts into 130 | * 'build_dir', and then to copy the assets to 'compile_dir'. 131 | */ 132 | copy: { 133 | build_app_assets: { 134 | files: [ 135 | { 136 | src: [ '**' ], 137 | dest: '<%%= build_dir %>/assets/', 138 | cwd: 'src/assets', 139 | expand: true 140 | } 141 | ] 142 | }, 143 | build_vendor_assets: { 144 | files: [ 145 | { 146 | src: [ '<%%= vendor_files.assets %>' ], 147 | dest: '<%%= build_dir %>/assets/', 148 | cwd: '.', 149 | expand: true, 150 | flatten: true 151 | } 152 | ] 153 | }, 154 | build_appjs: { 155 | files: [ 156 | { 157 | src: [ '<%%= app_files.js %>' ], 158 | dest: '<%%= build_dir %>/', 159 | cwd: '.', 160 | expand: true 161 | } 162 | ] 163 | }, 164 | build_vendorjs: { 165 | files: [ 166 | { 167 | src: [ '<%%= vendor_files.js %>' ], 168 | dest: '<%%= build_dir %>/', 169 | cwd: '.', 170 | expand: true 171 | } 172 | ] 173 | }, 174 | buildmock_vendorjs: { 175 | files: [ 176 | { 177 | src: [ '<%%= vendor_files.js %>', '<%%= test_files.js %>' ], 178 | dest: '<%%= build_dir %>/', 179 | cwd: '.', 180 | expand: true 181 | } 182 | ] 183 | }, 184 | build_vendorcss: { 185 | files: [ 186 | { 187 | src: [ '<%%= vendor_files.css %>' ], 188 | dest: '<%%= build_dir %>/', 189 | cwd: '.', 190 | expand: true 191 | } 192 | ] 193 | }, 194 | compile_assets: { 195 | files: [ 196 | { 197 | src: [ '**' ], 198 | dest: '<%%= compile_dir %>/assets', 199 | cwd: '<%%= build_dir %>/assets', 200 | expand: true 201 | } 202 | ] 203 | } 204 | }, 205 | 206 | /** 207 | * 'grunt concat' concatenates multiple source files into a single file. 208 | */ 209 | concat: { 210 | // The 'build_css' target concatenates compiled CSS and vendor CSS together. 211 | build_css: { 212 | src: [ 213 | '<%%= vendor_files.css %>', 214 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css' 215 | ], 216 | dest: '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css' 217 | }, 218 | // The 'compile_js' target concatenates app and vendor js code together. 219 | compile_js: { 220 | options: { 221 | banner: '<%%= meta.banner %>' 222 | }, 223 | src: [ 224 | '<%%= vendor_files.js %>', 225 | 'module.prefix', 226 | '<%%= build_dir %>/src/**/*.module.js', 227 | '<%%= build_dir %>/src/**/*.js', 228 | '<%%= html2js.app.dest %>', 229 | '<%%= html2js.common.dest %>', 230 | 'module.suffix' 231 | ], 232 | dest: '<%%= compile_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.js' 233 | } 234 | }, 235 | 236 | /** 237 | * 'grunt coffee' compiles the CoffeeScript sources. To work well with the 238 | * rest of the build, we have a separate compilation task for sources and 239 | * specs so they can go to different places. For example, we need the 240 | * sources to live with the rest of the copied JavaScript so we can include 241 | * it in the final build, but we don't want to include our specs there. 242 | */ 243 | coffee: { 244 | source: { 245 | options: { 246 | bare: true 247 | }, 248 | expand: true, 249 | cwd: '.', 250 | src: [ '<%%= app_files.coffee %>' ], 251 | dest: '<%%= build_dir %>', 252 | ext: '.js' 253 | } 254 | }, 255 | 256 | /** 257 | * 'ng-annotate' annotates the sources for safe minification. That is, it allows us 258 | * to code without the array syntax. 259 | */ 260 | ngAnnotate: { 261 | options: { 262 | singleQuotes: true 263 | }, 264 | build: { 265 | files:[ 266 | { 267 | src: [ '<%%= app_files.js %>' ], 268 | cwd: '<%%= build_dir %>', 269 | dest: '<%%= build_dir %>', 270 | expand: true 271 | }, 272 | ] 273 | }, 274 | }, 275 | 276 | /** 277 | * Minify the sources! 278 | */ 279 | uglify: { 280 | compile: { 281 | options: { 282 | banner: '<%%= meta.banner %>' 283 | }, 284 | files: { 285 | '<%%= concat.compile_js.dest %>': '<%%= concat.compile_js.dest %>' 286 | } 287 | } 288 | }, 289 | 290 | /** 291 | * `grunt-contrib-less` handles our LESS compilation and uglification automatically. 292 | * Only our 'main.less' file is included in compilation; all other files 293 | * must be imported from this file. 294 | */ 295 | less: { 296 | build: { 297 | files: { 298 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css': '<%%= app_files.less %>' 299 | } 300 | }, 301 | compile: { 302 | files: { 303 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css': '<%%= app_files.less %>' 304 | }, 305 | options: { 306 | cleancss: true, 307 | compress: true 308 | } 309 | } 310 | }, 311 | 312 | /** 313 | * 'jshint' defines the rules of our linter as well as which files we 314 | * should check. This file, all javascript sources, and all our unit tests 315 | * are linted based on the policies listed in 'options'. But we can also 316 | * specify exclusionary patterns by prefixing them with an exclamation 317 | * point (!); this is useful when code comes from a third party but is 318 | * nonetheless inside 'src/'. 319 | */ 320 | jshint: { 321 | src: [ 322 | '<%%= app_files.js %>' 323 | ], 324 | test: [ 325 | '<%%= app_files.jsunit %>' 326 | ], 327 | gruntfile: [ 328 | 'Gruntfile.js' 329 | ], 330 | options: { 331 | curly: true, 332 | immed: true, 333 | newcap: true, 334 | noarg: true, 335 | sub: true, 336 | boss: true, 337 | eqnull: true, 338 | globals: {} 339 | }, 340 | }, 341 | 342 | /** 343 | * 'coffeelint' does the same as 'jshint', but for CoffeeScript. 344 | * CoffeeScript is not the default in ngBoilerplate, so we're just using 345 | * the defaults here. 346 | */ 347 | coffeelint: { 348 | src: { 349 | files: { 350 | src: [ '<%%= app_files.coffee %>' ] 351 | } 352 | }, 353 | test: { 354 | files: { 355 | src: [ '<%%= app_files.coffeeunit %>' ] 356 | } 357 | } 358 | }, 359 | 360 | /** 361 | * HTML2JS is a Grunt plugin that takes all of your template files and 362 | * places them into JavaScript files as strings that are added to 363 | * AngularJS's template cache. This means that the templates too become 364 | * part of the initial payload as one JavaScript file. Neat! 365 | */ 366 | html2js: { 367 | // These are the templates from 'src/app'. 368 | app: { 369 | options: { 370 | base: 'src/app' 371 | }, 372 | src: [ '<%%= app_files.appTemplates %>' ], 373 | dest: '<%%= build_dir %>/templates-app.js' 374 | }, 375 | 376 | // These are the templates from 'src/common'. 377 | common: { 378 | options: { 379 | base: 'src/common' 380 | }, 381 | src: [ '<%%= app_files.commonTemplates %>' ], 382 | dest: '<%%= build_dir %>/templates-common.js' 383 | } 384 | }, 385 | 386 | /** 387 | * The 'index' task compiles the 'index.html' file as a Grunt template. CSS 388 | * and JS files co-exist here but they get split apart later. 389 | */ 390 | index: { 391 | 392 | /** 393 | * During development, we don't want to have wait for compilation, 394 | * concatenation, minification, etc. So to avoid these steps, we simply 395 | * add all script files directly to the '' of 'index.html'. The 396 | * 'src' property contains the list of included files. 397 | */ 398 | build: { 399 | appName: '<%= projectName %>', 400 | dir: '<%%= build_dir %>', 401 | src: [ 402 | '<%%= vendor_files.js %>', 403 | '<%%= build_dir %>/src/**/*.module.js', 404 | '<%%= build_dir %>/src/**/*.js', 405 | '<%%= html2js.common.dest %>', 406 | '<%%= html2js.app.dest %>', 407 | '<%%= vendor_files.css %>', 408 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css' 409 | ] 410 | }, 411 | /** 412 | * Identical to above, but with test_files included. 413 | * Good for using a mocked backend like $httpBackend. 414 | */ 415 | mock: { 416 | appName: 'mockApp', 417 | dir: '<%%= build_dir %>', 418 | src: [ 419 | '<%%= vendor_files.js %>', 420 | '<%%= test_files.js %>', 421 | '<%%= build_dir %>/src/**/*.module.js', 422 | '<%%= build_dir %>/src/**/*.js', 423 | '<%%= html2js.common.dest %>', 424 | '<%%= html2js.app.dest %>', 425 | '<%%= vendor_files.css %>', 426 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css' 427 | ] 428 | }, 429 | 430 | /** 431 | * When it is time to have a completely compiled application, we can 432 | * alter the above to include only a single JavaScript and a single CSS 433 | * file. Now we're back! 434 | */ 435 | compile: { 436 | appName: '<%= projectName %>', 437 | dir: '<%%= compile_dir %>', 438 | src: [ 439 | '<%%= concat.compile_js.dest %>', 440 | '<%%= build_dir %>/assets/<%%= pkg.name %>-<%%= pkg.version %>.css' 441 | ] 442 | } 443 | }, 444 | 445 | express: { 446 | devServer: { 447 | options: { 448 | port: 9000, 449 | hostname: 'localhost', 450 | serverreload: false, 451 | bases: 'build', 452 | livereload: true 453 | } 454 | } 455 | }, 456 | 457 | /** 458 | * The Karma configurations. 459 | */ 460 | karma: { 461 | options: { 462 | configFile: '<%%= build_dir %>/karma-unit.js' 463 | }, 464 | unit: { 465 | runnerPort: 9019, 466 | background: true 467 | }, 468 | continuous: { 469 | singleRun: true 470 | } 471 | }, 472 | 473 | /** 474 | * This task compiles the karma template so that changes to its file array 475 | * don't have to be managed manually. 476 | */ 477 | karmaconfig: { 478 | unit: { 479 | dir: '<%%= build_dir %>', 480 | src: [ 481 | '<%%= vendor_files.js %>', 482 | '<%%= html2js.app.dest %>', 483 | '<%%= html2js.common.dest %>', 484 | '<%%= test_files.js %>' 485 | ] 486 | } 487 | }, 488 | 489 | /** 490 | * And for rapid development, we have a watch set up that checks to see if 491 | * any of the files listed below change, and then to execute the listed 492 | * tasks when they do. This just saves us from having to type "grunt" into 493 | * the command-line every time we want to see what we're working on; we can 494 | * instead just leave "grunt watch" running in a background terminal. Set it 495 | * and forget it, as Ron Popeil used to tell us. 496 | * 497 | * But we don't need the same thing to happen for all the files. 498 | */ 499 | delta: { 500 | /** 501 | * By default, we want the Live Reload to work for all tasks; this is 502 | * overridden in some tasks (like this file) where browser resources are 503 | * unaffected. It runs by default on port 35729, which your browser 504 | * plugin should auto-detect. 505 | */ 506 | options: { 507 | livereload: true 508 | }, 509 | 510 | /** 511 | * When the Gruntfile changes, we just want to lint it. In fact, when 512 | * your Gruntfile changes, it will automatically be reloaded! 513 | * We also want to copy vendor files and rebuild index.html in case 514 | * vendor_files.js was altered (list of 3rd party vendor files installed by bower) 515 | */ 516 | gruntfile: { 517 | files: 'Gruntfile.js', 518 | tasks: [ 'jshint:gruntfile', 'clean:vendor', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build' ], 519 | options: { 520 | livereload: false 521 | } 522 | }, 523 | 524 | /** 525 | * When our JavaScript source files change, we want to run lint them and 526 | * run our unit tests. 527 | */ 528 | jssrc: { 529 | files: [ 530 | '<%%= app_files.js %>' 531 | ], 532 | tasks: [ 'jshint:src', 'karma:unit:run', 'copy:build_appjs', 'index:build' ] 533 | }, 534 | 535 | /** 536 | * When our CoffeeScript source files change, we want to run lint them and 537 | * run our unit tests. 538 | */ 539 | coffeesrc: { 540 | files: [ 541 | '<%%= app_files.coffee %>' 542 | ], 543 | tasks: [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs', 'index:build' ] 544 | }, 545 | 546 | /** 547 | * When assets are changed, copy them. Note that this will *not* copy new 548 | * files, so this is probably not very useful. 549 | */ 550 | assets: { 551 | files: [ 552 | 'src/assets/**/*' 553 | ], 554 | tasks: [ 'copy:build_app_assets' ] 555 | }, 556 | 557 | /** 558 | * When index.html changes, we need to compile it. 559 | */ 560 | html: { 561 | files: [ '<%%= app_files.html %>' ], 562 | tasks: [ 'index:build' ] 563 | }, 564 | 565 | /** 566 | * When our templates change, we only rewrite the template cache. 567 | */ 568 | tpls: { 569 | files: [ 570 | '<%%= app_files.appTemplates %>', 571 | '<%%= app_files.commonTemplates %>' 572 | ], 573 | tasks: [ 'html2js' ] 574 | }, 575 | 576 | /** 577 | * When the CSS files change, we need to compile and minify them. 578 | */ 579 | less: { 580 | files: [ 'src/**/*.less' ], 581 | tasks: [ 'less:build' ] 582 | }, 583 | 584 | /** 585 | * When a JavaScript unit test file changes, we only want to lint it and 586 | * run the unit tests. We don't want to do any live reloading. 587 | */ 588 | jsunit: { 589 | files: [ 590 | '<%%= app_files.jsunit %>' 591 | ], 592 | tasks: [ 'jshint:test', 'karma:unit:run' ], 593 | options: { 594 | livereload: false 595 | } 596 | }, 597 | 598 | /** 599 | * When a CoffeeScript unit test file changes, we only want to lint it and 600 | * run the unit tests. We don't want to do any live reloading. 601 | */ 602 | coffeeunit: { 603 | files: [ 604 | '<%%= app_files.coffeeunit %>' 605 | ], 606 | tasks: [ 'coffeelint:test', 'karma:unit:run' ], 607 | options: { 608 | livereload: false 609 | } 610 | } 611 | } 612 | }; 613 | 614 | 615 | /** ********************************************************************************* */ 616 | /** **************************** Project Configuration ****************************** */ 617 | // The following chooses some watch tasks based on whether we're running in mock mode or not. 618 | // Our watch (delta above) needs to run a different index task and copyVendorJs task 619 | // in several places if "grunt watchmock" is run. 620 | taskName = grunt.cli.tasks[0]; // the name of the task from the command line (e.g. "grunt watch" => "watch") 621 | var indexTask = taskName === 'watchmock' ? 'index:mock' : 'index:build'; 622 | var copyVendorJsTask = taskName === 'watchmock' ? 'copy:buildmock_vendorjs' : 'copy:build_vendorjs'; 623 | taskConfig.delta.gruntfile.tasks = [ 'jshint:gruntfile', 'clean:vendor', copyVendorJsTask, 'copy:build_vendorcss', indexTask ]; 624 | taskConfig.delta.jssrc.tasks = [ 'jshint:src', 'copy:build_appjs', indexTask ]; 625 | taskConfig.delta.coffeesrc.tasks = [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs', indexTask ]; 626 | taskConfig.delta.html.tasks = [ indexTask ]; 627 | 628 | // Take the big config objects we defined above, combine them, and feed them into grunt 629 | grunt.initConfig(_.assign(taskConfig, fileConfig)); 630 | 631 | // In order to make it safe to just compile or copy *only* what was changed, 632 | // we need to ensure we are starting from a clean, fresh build. So we rename 633 | // the 'watch' task to 'delta' (that's why the configuration var above is 634 | // 'delta') and then add a new task called 'watch' that does a clean build 635 | // before watching for changes. 636 | grunt.renameTask('watch', 'delta'); 637 | grunt.registerTask('watch', [ 'build', 'karma:unit', 'express', 'delta' ]); 638 | // watchmock is just like watch, but includes testing resources for using $httpBackend 639 | grunt.registerTask('watchmock', [ 'buildmock', 'karma:unit', 'express', 'delta' ]); 640 | 641 | // The default task is to build and compile. 642 | grunt.registerTask('default', [ 'build', 'compile' ]); 643 | 644 | // The 'build' task gets your app ready to run for development and testing. 645 | grunt.registerTask('build', [ 646 | 'clean:all', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build', 647 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets', 648 | 'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:build', 'karmaconfig', 649 | 'karma:continuous' 650 | ]); 651 | 652 | // just like build, but includes testing resources for using $httpBackend and switches to mock application in index.html 653 | grunt.registerTask('buildmock', [ 654 | 'clean:all', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build', 655 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets', 656 | 'copy:build_appjs', 'copy:buildmock_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:mock', 'karmaconfig', 657 | 'karma:continuous' 658 | ]); 659 | 660 | // The 'compile' task gets your app ready for deployment by concatenating and minifying your code. 661 | // Note - compile builds off of the build dir (look at concat:compile_js), so run grunt build before grunt compile 662 | grunt.registerTask('compile', [ 663 | 'less:compile', 'concat:build_css', 'copy:compile_assets', 'concat:compile_js', 'uglify', 'index:compile' 664 | ]); 665 | 666 | // The debug task is just like watch without any of the testing. 667 | // This is good for debugging issues that cause 'grunt watch' to fail due to test errors. 668 | // Just 'grunt debug' and debug your app within a browser, then go back to grunt watch once you've 669 | // fixed the problem. You'll usually see the source of the problem right away in the console. 670 | grunt.registerTask('debug', [ 671 | 'clean:all', 'html2js', 'jshint', 'less:build', 672 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets', 673 | 'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'ngAnnotate:build', 'index:build', 'express', 'delta' 674 | ]); 675 | 676 | // A utility function to get all app JavaScript sources. 677 | function filterForJS (files) { 678 | return files.filter(function (file) { 679 | return file.match(/\.js$/); 680 | }); 681 | } 682 | 683 | // A utility function to get all app CSS sources. 684 | function filterForCSS (files) { 685 | return files.filter( function (file) { 686 | return file.match(/\.css$/); 687 | }); 688 | } 689 | 690 | // The index.html template includes the stylesheet and javascript sources 691 | // based on dynamic names calculated in this Gruntfile. This task assembles 692 | // the list into variables for the template to use and then runs the 693 | // compilation. 694 | grunt.registerMultiTask('index', 'Process index.html template', function () { 695 | var dirRE = new RegExp('^(' + grunt.config('build_dir') + '|' + grunt.config('compile_dir') + ')\/', 'g'); 696 | 697 | // this.fileSrc comes from either build:src, compile:src, or karmaconfig:src in the index config defined above 698 | // see - http://gruntjs.com/api/inside-tasks#this.filessrc for documentation 699 | var jsFiles = filterForJS(this.filesSrc).map(function (file) { 700 | return file.replace(dirRE, ''); 701 | }); 702 | var cssFiles = filterForCSS(this.filesSrc).map(function (file) { 703 | return file.replace(dirRE, ''); 704 | }); 705 | 706 | var app = this.data.appName; 707 | 708 | // this.data.dir comes from either build:dir, compile:dir, or karmaconfig:dir in the index config defined above 709 | // see - http://gruntjs.com/api/inside-tasks#this.data for documentation 710 | grunt.file.copy('src/index.html', this.data.dir + '/index.html', { 711 | process: function (contents, path) { 712 | // These are the variables looped over in our index.html exposed as "scripts", "styles", and "version" 713 | return grunt.template.process(contents, { 714 | data: { 715 | appName: app, 716 | scripts: jsFiles, 717 | styles: cssFiles, 718 | version: grunt.config('pkg.version'), 719 | author: grunt.config('pkg.author'), 720 | date: grunt.template.today("yyyy") 721 | } 722 | }); 723 | } 724 | }); 725 | }); 726 | 727 | // In order to avoid having to specify manually the files needed for karma to 728 | // run, we use grunt to manage the list for us. The 'karma/*' files are 729 | // compiled as grunt templates for use by Karma. Yay! 730 | grunt.registerMultiTask('karmaconfig', 'Process karma config templates', function () { 731 | var jsFiles = filterForJS(this.filesSrc); 732 | 733 | grunt.file.copy('karma/karma-unit.tpl.js', grunt.config('build_dir') + '/karma-unit.js', { 734 | process: function (contents, path) { 735 | // This is the variable looped over in the karma template of our index.html exposed as "scripts" 736 | return grunt.template.process(contents, { 737 | data: { 738 | scripts: jsFiles 739 | } 740 | }); 741 | } 742 | }); 743 | }); 744 | 745 | }; 746 | --------------------------------------------------------------------------------