├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── images │ ├── angular.png │ ├── browserify.png │ └── gulp.png ├── index.html ├── js │ ├── constants.js │ ├── controllers │ │ ├── example.js │ │ └── index.js │ ├── directives │ │ ├── example.js │ │ └── index.js │ ├── filters │ │ ├── example.js │ │ └── index.js │ ├── main.js │ ├── on_config.js │ ├── on_run.js │ └── services │ │ ├── example.js │ │ └── index.js ├── styles │ ├── _typography.scss │ ├── _vars.scss │ └── main.scss └── views │ ├── directives │ └── example.html │ └── home.html ├── gulp ├── config.js ├── index.js ├── tasks │ ├── browserSync.js │ ├── browserify.js │ ├── clean.js │ ├── deploy.js │ ├── development.js │ ├── eslint.js │ ├── fonts.js │ ├── gzip.js │ ├── images.js │ ├── production.js │ ├── protractor.js │ ├── styles.js │ ├── test.js │ ├── unit.js │ ├── views.js │ └── watch.js └── util │ ├── bundleLogger.js │ ├── handleErrors.js │ ├── scriptFilter.js │ └── testServer.js ├── gulpfile.babel.js ├── package.json └── test ├── .eslintrc ├── e2e ├── example_spec.js └── routes_spec.js ├── karma.conf.js ├── protractor.conf.js └── unit ├── constants_spec.js ├── controllers └── example_spec.js ├── directives └── example_spec.js ├── filters └── example_spec.js └── services └── example_spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "ecmaFeatures": { 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | "quotes": [2, "single"], 13 | "eol-last": 0, 14 | "no-debugger": 1, 15 | "no-mixed-requires": 0, 16 | "no-underscore-dangle": 0, 17 | "no-multi-spaces": 0, 18 | "no-trailing-spaces": 0, 19 | "no-extra-boolean-cast": 0, 20 | "no-unused-vars": 2, 21 | "new-cap": 0, 22 | "camelcase": 2 23 | } 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Compiled binary addons (http://nodejs.org/api/addons.html) 19 | build/Release 20 | 21 | # Dependency directories 22 | node_modules 23 | bower_components 24 | 25 | # Build files 26 | build 27 | templates.js 28 | 29 | #webstorm 30 | .idea 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2" 4 | sudo: false 5 | 6 | script: 7 | - ./node_modules/.bin/gulp eslint 8 | - ./node_modules/.bin/gulp test 9 | 10 | cache: 11 | directories: 12 | - node_modules 13 | 14 | addons: 15 | sauce_connect: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jake Marsh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angularjs-gulp-browserify-boilerplate 2 | ===================================== 3 | **:exclamation: Warning: This boilerplate is no longer maintained and I would not recommend starting a new project with it.** 4 | 5 | --- 6 | 7 | [![Build Status](https://travis-ci.org/jakemmarsh/angularjs-gulp-browserify-boilerplate.svg)](https://travis-ci.org/jakemmarsh/angularjs-gulp-browserify-boilerplate) [![dependencies Status](https://david-dm.org/jakemmarsh/angularjs-gulp-browserify-boilerplate/status.svg)](https://david-dm.org/jakemmarsh/angularjs-gulp-browserify-boilerplate) [![devDependency Status](https://david-dm.org/jakemmarsh/angularjs-gulp-browserify-boilerplate/dev-status.svg)](https://david-dm.org/jakemmarsh/angularjs-gulp-browserify-boilerplate#info=devDependencies) 8 | 9 | A boilerplate using AngularJS, SASS, Gulp, and Browserify that also utilizes [these best AngularJS practices](https://github.com/toddmotto/angularjs-styleguide) and Gulp best practices from [this resource](https://github.com/greypants/gulp-starter). 10 | 11 | [View contributors](https://github.com/jakemmarsh/angularjs-gulp-browserify-boilerplate/graphs/contributors) 12 | 13 | --- 14 | 15 | ### Getting up and running 16 | 17 | 1. Clone this repo from `https://github.com/jakemmarsh/angularjs-gulp-browserify-boilerplate.git` 18 | 2. Run `npm install` from the root directory 19 | 3. Run `npm run dev` 20 | 4. Your browser will automatically be opened and directed to the browser-sync proxy address 21 | 5. To prepare assets for production, run the `npm run build` script (Note: the production task does not fire up the express server, and won't provide you with browser-sync's live reloading. Simply use `npm run dev` during development. More information below) 22 | 23 | Now that `npm run dev` is running, the server is up as well and serving files from the `/build` directory. Any changes in the `/app` directory will be automatically processed by Gulp and the changes will be injected to any open browsers pointed at the proxy address. 24 | 25 | #### Other resources 26 | 27 | - [Yeoman generator](https://github.com/alferov/generator-angular-gulp-browserify) 28 | - [Cordova-friendly fork](https://github.com/StrikeForceZero/angularjs-cordova-gulp-browserify-boilerplate) 29 | 30 | --- 31 | 32 | This boilerplate uses the latest versions of the following libraries: 33 | 34 | - [AngularJS](http://angularjs.org/) 35 | - [SASS](http://sass-lang.com/) 36 | - [Gulp](http://gulpjs.com/) 37 | - [Browserify](http://browserify.org/) 38 | 39 | Along with many Gulp libraries (these can be seen in either `package.json`, or at the top of each task in `/gulp/tasks/`). 40 | 41 | --- 42 | 43 | ### AngularJS 44 | 45 | AngularJS is a MVW (Model-View-Whatever) Javascript Framework for creating single-page web applications. In this boilerplate, it is used for all the application routing as well as all of the frontend views and logic. 46 | 47 | The AngularJS files are all located within `/app/js`, structured in the following manner: 48 | 49 | ``` 50 | /controllers 51 | index.js (the main module on which all controllers will be mounted, loaded in main.js) 52 | example.js 53 | /directives 54 | index.js (the main module on which all directives will be mounted, loaded in main.js) 55 | example.js 56 | /filters 57 | index.js (the main module on which all filters will be mounted, loaded in main.js) 58 | example.js 59 | /services 60 | index.js (the main module on which all services will be mounted, loaded in main.js) 61 | example.js 62 | constants.js (any constant values that you want to make available to Angular) 63 | main.js (the main file read by Browserify, also where the application is defined and bootstrapped) 64 | on_run.js (any functions or logic that need to be executed on app.run) 65 | on_config.js (all route definitions and any logic that need to be executed on app.config) 66 | templates.js (this is created via Gulp by compiling your views, and will not be present beforehand) 67 | ``` 68 | 69 | ##### Module organization 70 | 71 | Controllers, services, directives, etc. should all be placed within their respective folders, and will be automatically required and mounted via their respective `index.js` using `bulk-require`. All modules must export an object of the format: 72 | 73 | ```javascript 74 | const ExampleModule = function() {}; 75 | 76 | export default { 77 | name: 'ExampleModule', 78 | fn: ExampleModule 79 | }; 80 | 81 | ``` 82 | 83 | ##### Dependency injection 84 | 85 | Dependency injection is carried out with the `ng-annotate` library. In order to take advantage of this, a simple directive prologue of the format: 86 | 87 | ```js 88 | function MyService($http) { 89 | 'ngInject'; 90 | ... 91 | } 92 | ``` 93 | 94 | needs to be added at the very beginning of any Angular functions/modules. The Gulp tasks will then take care of adding any dependency injection, requiring you to only specify the dependencies within the function parameters and nothing more. 95 | 96 | --- 97 | 98 | ### SASS 99 | 100 | SASS, standing for 'Syntactically Awesome Style Sheets', is a CSS extension language adding things like extending, variables, and mixins to the language. This boilerplate provides a barebones file structure for your styles, with explicit imports into `app/styles/main.scss`. A Gulp task (discussed later) is provided for compilation and minification of the stylesheets based on this file. 101 | 102 | --- 103 | 104 | ### Browserify 105 | 106 | Browserify is a Javascript file and module loader, allowing you to `require('modules')` in all of your files in the same manner as you would on the backend in a node.js environment. The bundling and compilation is then taken care of by Gulp, discussed below. 107 | 108 | --- 109 | 110 | ### Gulp 111 | 112 | Gulp is a "streaming build system", providing a very fast and efficient method for running your build tasks. 113 | 114 | ##### Web Server 115 | 116 | Gulp is used here to provide a very basic node/Express web server for viewing and testing your application as you build. It serves static files from the `build/` directory, leaving routing up to AngularJS. All Gulp tasks are configured to automatically reload the server upon file changes. The application is served to `localhost:3002` once you run the `npm run dev` script. To take advantage of the fast live reload injection provided by browser-sync, you must load the site at the proxy address (within this boilerplate will by default be `localhost:3000`). To change the settings related to live-reload or browser-sync, you can access the UI at `localhost:3001`. 117 | 118 | ##### Scripts 119 | 120 | A number of build processes are automatically run on all of our Javascript files, run in the following order: 121 | 122 | - **JSHint:** Gulp is currently configured to run a JSHint task before processing any Javascript files. This will show any errors in your code in the console, but will not prevent compilation or minification from occurring. 123 | - **Browserify:** The main build process run on any Javascript files. This processes any of the `require('module')` statements, compiling the files as necessary. 124 | - **Babelify:** This uses [babelJS](https://babeljs.io/) to provide support for ES6+ features. 125 | - **Debowerify:** Parses `require()` statements in your code, mapping them to `bower_components` when necessary. This allows you to use and include bower components just as you would npm modules. 126 | - **ngAnnotate:** This will automatically add the correct dependency injection to any AngularJS files, as mentioned previously. 127 | - **Uglifyify:** This will minify the file created by Browserify and ngAnnotate. 128 | 129 | The resulting file (`main.js`) is placed inside the directory `/build/js/`. 130 | 131 | ##### Styles 132 | 133 | Just one plugin is necessary for processing our SASS files, and that is `gulp-sass`. This will read the `main.scss` file, processing and importing any dependencies and then minifying the result. This file (`main.css`) is placed inside the directory `/build/css/`. 134 | 135 | - **gulp-autoprefixer:** Gulp is currently configured to run autoprefixer after compiling the scss. Autoprefixer will use the data based on current browser popularity and property support to apply prefixes for you. Autoprefixer is recommended by Google and used in Twitter, WordPress, Bootstrap and CodePen. 136 | 137 | ##### Images 138 | 139 | Any images placed within `/app/images` will be automatically copied to the `build/images` directory. If running `npm run build`, they will also be compressed via imagemin. 140 | 141 | ##### Views 142 | 143 | When any changes are made to the `index.html` file, the new file is simply copied to the `/build/` directory without any changes occurring. 144 | 145 | Files inside `/app/views/`, on the other hand, go through a slightly more complex process. The `gulp-angular-templatecache` module is used in order to process all views/partials, creating the `template.js` file briefly mentioned earlier. This file will contain all the views, now in Javascript format inside Angular's `$templateCache` service. This will allow us to include them in our Javascript minification process, as well as avoid extra HTTP requests for our views. 146 | 147 | ##### Watching files 148 | 149 | All of the Gulp processes mentioned above are run automatically when any of the corresponding files in the `/app` directory are changed, and this is thanks to our Gulp watch tasks. Running `npm run dev` will begin watching all of these files, while also serving to `localhost:3002`, and with browser-sync proxy running at `localhost:3000` (by default). 150 | 151 | ##### Production Task 152 | 153 | Just as there is the `npm run dev` command for development, there is also a `npm run build` command for putting your project into a production-ready state. This will run each of the tasks, while also adding the image minification task discussed above. There is also an empty deploy task (run with `npm run deploy`) that is included when running the production task. This deploy task can be fleshed out to automatically push your production-ready site to your hosting setup. 154 | 155 | **Reminder:** When running the production task, gulp will not fire up the express server and serve your index.html. This task is designed to be run before the `deploy` step that may copy the files from `/build` to a production web server. 156 | 157 | ##### Pre-compressing text assets 158 | 159 | When building with `npm run build`, a pre-compressed file is generated in addition to uncompressed file (.html.gz, .js.gz, css.gz). This is done to enable web servers serve compressed content without having to compress it on the fly. Pre-compression is handled by `gzip` task. 160 | 161 | ##### Testing 162 | 163 | A Gulp tasks also exists for running the test framework (discussed in detail below). Running `gulp test` will run any and all tests inside the `/test` directory and show the results (and any errors) in the terminal. 164 | 165 | --- 166 | 167 | ### Testing 168 | 169 | This boilerplate also includes a simple framework for unit and end-to-end (e2e) testing via [Karma](http://karma-runner.github.io/) and [Jasmine](http://jasmine.github.io/). In order to test AngularJS modules, the [angular.mocks](https://docs.angularjs.org/api/ngMock/object/angular.mock) module is used. 170 | 171 | All of the tests can be run at once with the command `npm test`. However, the tests are broken up into two main categories: 172 | 173 | ##### End-to-End (e2e) Tests 174 | 175 | e2e tests, as hinted at by the name, consist of tests that involve multiple modules or require interaction between modules, similar to integration tests. These tests are carried out using the Angular library [Protractor](https://github.com/angular/protractor), which also utilizes Jasmine. The goal is to ensure that the flow of your application is performing as designed from start to finish. 176 | 177 | In this boilerplate, two end-to-end test examples are provided: 178 | 179 | - `routes_spec.js`, which tests the functionality of our AngularJS routing 180 | - `example_spec.js`, which tests the functionality of the example route, controller, and view 181 | 182 | More examples can be seen at the above link for Protractor. 183 | 184 | All e2e tests are run with `npm run protractor`. 185 | 186 | ##### Unit Tests 187 | 188 | Unit tests are used to test a single module (or "unit") at a time in order to ensure that each module performs as intended individually. In AngularJS this could be thought of as a single controller, directive, filter, service, etc. That is how the unit tests are organized in this boilerplate. 189 | 190 | An example test is provided for the following types of AngularJS modules: 191 | 192 | - `unit/controllers/example_spec.js` 193 | - `unit/services/example_spec.js` 194 | - `unit/directives/example_spec.js` 195 | - `unit/constants_spec.js` 196 | 197 | All unit tests are run with `npm run unit`. When running unit tests, code coverage is simultaneously calculated and output as an HTML file to the `/coverage` directory. 198 | -------------------------------------------------------------------------------- /app/images/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakemmarsh/angularjs-gulp-browserify-boilerplate/5cd63d2edb8f37c59710a7dd7494373c246c9837/app/images/angular.png -------------------------------------------------------------------------------- /app/images/browserify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakemmarsh/angularjs-gulp-browserify-boilerplate/5cd63d2edb8f37c59710a7dd7494373c246c9837/app/images/browserify.png -------------------------------------------------------------------------------- /app/images/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakemmarsh/angularjs-gulp-browserify-boilerplate/5cd63d2edb8f37c59710a7dd7494373c246c9837/app/images/gulp.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/js/constants.js: -------------------------------------------------------------------------------- 1 | const AppSettings = { 2 | appTitle: 'Example Application', 3 | apiUrl: '/api/v1' 4 | }; 5 | 6 | export default AppSettings; 7 | -------------------------------------------------------------------------------- /app/js/controllers/example.js: -------------------------------------------------------------------------------- 1 | function ExampleCtrl() { 2 | 3 | // ViewModel 4 | const vm = this; 5 | 6 | vm.title = 'AngularJS, Gulp, and Browserify! Written with keyboards and love!'; 7 | vm.number = 1234; 8 | 9 | } 10 | 11 | export default { 12 | name: 'ExampleCtrl', 13 | fn: ExampleCtrl 14 | }; 15 | -------------------------------------------------------------------------------- /app/js/controllers/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | const bulk = require('bulk-require'); 4 | const controllersModule = angular.module('app.controllers', []); 5 | const controllers = bulk(__dirname, ['./**/!(*index|*.spec).js']); 6 | 7 | function declare(controllerMap) { 8 | Object.keys(controllerMap).forEach((key) => { 9 | let item = controllerMap[key]; 10 | 11 | if (!item) { 12 | return; 13 | } 14 | 15 | if (item.fn && typeof item.fn === 'function') { 16 | controllersModule.controller(item.name, item.fn); 17 | } else { 18 | declare(item); 19 | } 20 | }); 21 | } 22 | 23 | declare(controllers); 24 | 25 | export default controllersModule; 26 | -------------------------------------------------------------------------------- /app/js/directives/example.js: -------------------------------------------------------------------------------- 1 | function ExampleDirective() { 2 | 3 | return { 4 | restrict: 'EA', 5 | templateUrl: 'directives/example.html', 6 | scope: { 7 | title: '@', 8 | message: '@clickMessage' 9 | }, 10 | link: (scope, element) => { 11 | element.on('click', () => { 12 | window.alert('Element clicked: ' + scope.message); 13 | }); 14 | } 15 | }; 16 | } 17 | 18 | export default { 19 | name: 'exampleDirective', 20 | fn: ExampleDirective 21 | }; 22 | -------------------------------------------------------------------------------- /app/js/directives/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | const bulk = require('bulk-require'); 4 | const directivesModule = angular.module('app.directives', []); 5 | const directives = bulk(__dirname, ['./**/!(*index|*.spec).js']); 6 | 7 | function declare(directiveMap) { 8 | Object.keys(directiveMap).forEach((key) => { 9 | let item = directiveMap[key]; 10 | 11 | if (!item) { 12 | return; 13 | } 14 | 15 | if (item.fn && typeof item.fn === 'function') { 16 | directivesModule.directive(item.name, item.fn); 17 | } else { 18 | declare(item); 19 | } 20 | }); 21 | } 22 | 23 | declare(directives); 24 | 25 | export default directivesModule; 26 | -------------------------------------------------------------------------------- /app/js/filters/example.js: -------------------------------------------------------------------------------- 1 | function ExampleFilter() { 2 | 3 | return function(input) { 4 | return input.replace(/keyboard/ig,'leopard'); 5 | }; 6 | 7 | } 8 | 9 | export default { 10 | name: 'ExampleFilter', 11 | fn: ExampleFilter 12 | }; -------------------------------------------------------------------------------- /app/js/filters/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | const bulk = require('bulk-require'); 4 | const filtersModule = angular.module('app.filters', []); 5 | const filters = bulk(__dirname, ['./**/!(*index|*.spec).js']); 6 | 7 | function declare(filterMap) { 8 | Object.keys(filterMap).forEach((key) => { 9 | let item = filterMap[key]; 10 | 11 | if (!item) { 12 | return; 13 | } 14 | 15 | if (item.fn && typeof item.fn === 'function') { 16 | filtersModule.filter(item.name, item.fn); 17 | } else { 18 | declare(item); 19 | } 20 | }); 21 | } 22 | 23 | declare(filters); 24 | 25 | export default filtersModule; 26 | -------------------------------------------------------------------------------- /app/js/main.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // angular modules 4 | import constants from './constants'; 5 | import onConfig from './on_config'; 6 | import onRun from './on_run'; 7 | import 'angular-ui-router'; 8 | import './templates'; 9 | import './filters'; 10 | import './controllers'; 11 | import './services'; 12 | import './directives'; 13 | 14 | // create and bootstrap application 15 | const requires = [ 16 | 'ui.router', 17 | 'templates', 18 | 'app.filters', 19 | 'app.controllers', 20 | 'app.services', 21 | 'app.directives' 22 | ]; 23 | 24 | // mount on window for testing 25 | window.app = angular.module('app', requires); 26 | 27 | angular.module('app').constant('AppSettings', constants); 28 | 29 | angular.module('app').config(onConfig); 30 | 31 | angular.module('app').run(onRun); 32 | 33 | angular.bootstrap(document, ['app'], { 34 | strictDi: true 35 | }); 36 | -------------------------------------------------------------------------------- /app/js/on_config.js: -------------------------------------------------------------------------------- 1 | function OnConfig($stateProvider, $locationProvider, $urlRouterProvider, $compileProvider) { 2 | 'ngInject'; 3 | 4 | if (process.env.NODE_ENV === 'production') { 5 | $compileProvider.debugInfoEnabled(false); 6 | } 7 | 8 | $locationProvider.html5Mode({ 9 | enabled: true, 10 | requireBase: false 11 | }); 12 | 13 | $stateProvider 14 | .state('Home', { 15 | url: '/', 16 | controller: 'ExampleCtrl as home', 17 | templateUrl: 'home.html', 18 | title: 'Home' 19 | }); 20 | 21 | $urlRouterProvider.otherwise('/'); 22 | 23 | } 24 | 25 | export default OnConfig; 26 | -------------------------------------------------------------------------------- /app/js/on_run.js: -------------------------------------------------------------------------------- 1 | function OnRun($rootScope, AppSettings) { 2 | 'ngInject'; 3 | 4 | // change page title based on state 5 | $rootScope.$on('$stateChangeSuccess', (event, toState) => { 6 | $rootScope.pageTitle = ''; 7 | 8 | if (toState.title) { 9 | $rootScope.pageTitle += toState.title; 10 | $rootScope.pageTitle += ' \u2014 '; 11 | } 12 | 13 | $rootScope.pageTitle += AppSettings.appTitle; 14 | }); 15 | 16 | } 17 | 18 | export default OnRun; 19 | -------------------------------------------------------------------------------- /app/js/services/example.js: -------------------------------------------------------------------------------- 1 | function ExampleService($http) { 2 | 'ngInject'; 3 | 4 | const service = {}; 5 | 6 | service.get = function() { 7 | return new Promise((resolve, reject) => { 8 | $http.get('apiPath').success((data) => { 9 | resolve(data); 10 | }).error((err, status) => { 11 | reject(err, status); 12 | }); 13 | }); 14 | }; 15 | 16 | return service; 17 | 18 | } 19 | 20 | export default { 21 | name: 'ExampleService', 22 | fn: ExampleService 23 | }; 24 | -------------------------------------------------------------------------------- /app/js/services/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | const bulk = require('bulk-require'); 4 | const servicesModule = angular.module('app.services', []); 5 | const services = bulk(__dirname, ['./**/!(*index|*.spec).js']); 6 | 7 | function declare(serviceMap) { 8 | Object.keys(serviceMap).forEach((key) => { 9 | let item = serviceMap[key]; 10 | 11 | if (!item) { 12 | return; 13 | } 14 | 15 | if (item.fn && typeof item.fn === 'function') { 16 | servicesModule.service(item.name, item.fn); 17 | } else { 18 | declare(item); 19 | } 20 | }); 21 | } 22 | 23 | declare(services); 24 | 25 | export default servicesModule; 26 | -------------------------------------------------------------------------------- /app/styles/_typography.scss: -------------------------------------------------------------------------------- 1 | p { 2 | margin-bottom: 1em; 3 | } 4 | 5 | .heading { 6 | margin-bottom: 0.618em; 7 | 8 | &.-large { 9 | font-size: $font-size--lg; 10 | font-weight: bold; 11 | line-height: $half-space * 3 / 2; 12 | } 13 | 14 | &.-medium { 15 | font-size: $font-size--md; 16 | font-weight: normal; 17 | line-height: $half-space; 18 | } 19 | 20 | &.-small { 21 | font-size: $font-size--sm; 22 | font-weight: bold; 23 | line-height: $half-space * 2 / 3; 24 | } 25 | 26 | &.-smallest { 27 | font-size: $font-size--xs; 28 | font-weight: bold; 29 | } 30 | } 31 | 32 | h1 { 33 | @extend .heading.-large; 34 | } 35 | 36 | h2 { 37 | @extend .heading.-medium; 38 | } 39 | 40 | h3 { 41 | @extend .heading.-small; 42 | } -------------------------------------------------------------------------------- /app/styles/_vars.scss: -------------------------------------------------------------------------------- 1 | // colors 2 | $font-color--dark: #333; 3 | $font-color--light: #fff; 4 | $background--light: #eee; 5 | $background--dark: #222; 6 | $blue: #1f8de2; 7 | $green: #1fe27b; 8 | $red: #e21f3f; 9 | 10 | // spacing 11 | $full-space: 40px; 12 | $half-space: 20px; 13 | 14 | // font sizing 15 | $font-size--xs: 10px; 16 | $font-size--sm: 12px; 17 | $font-size--md: 16px; 18 | $font-size--lg: 24px; 19 | $font-size--xl: 32px; -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'vars'; 2 | @import 'typography'; 3 | 4 | body { 5 | font-family: Helvetica, sans-serif; 6 | color: $font-color--dark; 7 | background-color: $background--light; 8 | padding: $half-space; 9 | } -------------------------------------------------------------------------------- /app/views/directives/example.html: -------------------------------------------------------------------------------- 1 |
2 |

Directive title: {{title}}

3 |

This is an example of a directive, click me!

4 |
5 | -------------------------------------------------------------------------------- /app/views/home.html: -------------------------------------------------------------------------------- 1 |

{{ home.title | ExampleFilter }}

2 | 3 |

Here is a fancy number served up courtesy of Angular: {{ home.number }}

4 | 5 | 6 | 7 | 8 | 9 |
10 |
Directive is not loaded.
11 | -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | browserPort: 3000, 4 | UIPort: 3001, 5 | testPort: 3002, 6 | 7 | sourceDir: './app/', 8 | buildDir: './build/', 9 | 10 | styles: { 11 | src: 'app/styles/**/*.scss', 12 | dest: 'build/css', 13 | prodSourcemap: false, 14 | sassIncludePaths: [] 15 | }, 16 | 17 | scripts: { 18 | src: 'app/js/**/*.js', 19 | dest: 'build/js', 20 | test: 'test/**/*.js', 21 | gulp: 'gulp/**/*.js' 22 | }, 23 | 24 | images: { 25 | src: 'app/images/**/*', 26 | dest: 'build/images' 27 | }, 28 | 29 | fonts: { 30 | src: ['app/fonts/**/*'], 31 | dest: 'build/fonts' 32 | }, 33 | 34 | assetExtensions: [ 35 | 'js', 36 | 'css', 37 | 'png', 38 | 'jpe?g', 39 | 'gif', 40 | 'svg', 41 | 'eot', 42 | 'otf', 43 | 'ttc', 44 | 'ttf', 45 | 'woff2?' 46 | ], 47 | 48 | views: { 49 | index: 'app/index.html', 50 | src: 'app/views/**/*.html', 51 | dest: 'app/js' 52 | }, 53 | 54 | gzip: { 55 | src: 'build/**/*.{html,xml,json,css,js,js.map,css.map}', 56 | dest: 'build/', 57 | options: {} 58 | }, 59 | 60 | browserify: { 61 | bundleName: 'main.js', 62 | prodSourcemap: false 63 | }, 64 | 65 | test: { 66 | karma: 'test/karma.conf.js', 67 | protractor: 'test/protractor.conf.js' 68 | }, 69 | 70 | init: function() { 71 | this.views.watch = [ 72 | this.views.index, 73 | this.views.src 74 | ]; 75 | 76 | return this; 77 | } 78 | 79 | }.init(); 80 | -------------------------------------------------------------------------------- /gulp/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import gulp from 'gulp'; 3 | import onlyScripts from './util/scriptFilter'; 4 | 5 | const tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts); 6 | 7 | // Ensure process ends after all Gulp tasks are finished 8 | gulp.on('stop', function () { 9 | if ( !global.isWatching ) { 10 | process.nextTick(function () { 11 | process.exit(0); 12 | }); 13 | } 14 | }); 15 | 16 | tasks.forEach((task) => { 17 | require('./tasks/' + task); 18 | }); 19 | -------------------------------------------------------------------------------- /gulp/tasks/browserSync.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import url from 'url'; 3 | import browserSync from 'browser-sync'; 4 | import gulp from 'gulp'; 5 | 6 | gulp.task('browserSync', function() { 7 | 8 | const DEFAULT_FILE = 'index.html'; 9 | const ASSET_EXTENSION_REGEX = new RegExp(`\\b(?!\\?)\\.(${config.assetExtensions.join('|')})\\b(?!\\.)`, 'i'); 10 | 11 | browserSync.init({ 12 | server: { 13 | baseDir: config.buildDir, 14 | middleware: function(req, res, next) { 15 | let fileHref = url.parse(req.url).href; 16 | 17 | if ( !ASSET_EXTENSION_REGEX.test(fileHref) ) { 18 | req.url = '/' + DEFAULT_FILE; 19 | } 20 | 21 | return next(); 22 | } 23 | }, 24 | port: config.browserPort, 25 | ui: { 26 | port: config.UIPort 27 | }, 28 | ghostMode: { 29 | links: false 30 | } 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import gulpif from 'gulp-if'; 3 | import source from 'vinyl-source-stream'; 4 | import sourcemaps from 'gulp-sourcemaps'; 5 | import buffer from 'vinyl-buffer'; 6 | import streamify from 'gulp-streamify'; 7 | import watchify from 'watchify'; 8 | import browserify from 'browserify'; 9 | import babelify from 'babelify'; 10 | import uglify from 'gulp-uglify'; 11 | import browserSync from 'browser-sync'; 12 | import debowerify from 'debowerify'; 13 | import ngAnnotate from 'browserify-ngannotate'; 14 | import bulkify from 'bulkify'; 15 | import envify from 'envify'; 16 | import handleErrors from '../util/handleErrors'; 17 | import bundleLogger from '../util/bundleLogger'; 18 | import config from '../config'; 19 | 20 | // Based on: http://blog.avisi.nl/2014/04/25/how-to-keep-a-fast-build-with-browserify-and-reactjs/ 21 | function buildScript(file) { 22 | 23 | const shouldCreateSourcemap = !global.isProd || config.browserify.prodSourcemap; 24 | 25 | let bundler = browserify({ 26 | entries: [config.sourceDir + 'js/' + file], 27 | debug: shouldCreateSourcemap, 28 | cache: {}, 29 | packageCache: {}, 30 | fullPaths: !global.isProd 31 | }); 32 | 33 | if ( !global.isProd ) { 34 | bundler = watchify(bundler); 35 | 36 | bundler.on('update', rebundle); 37 | } 38 | 39 | const transforms = [ 40 | { name: babelify, options: {} }, 41 | { name: debowerify, options: {} }, 42 | { name: ngAnnotate, options: {} }, 43 | { name: 'brfs', options: {} }, 44 | { name: bulkify, options: {} }, 45 | { name: envify, options: {} } 46 | ]; 47 | 48 | transforms.forEach(function(transform) { 49 | bundler.transform(transform.name, transform.options); 50 | }); 51 | 52 | function rebundle() { 53 | bundleLogger.start(); 54 | 55 | const stream = bundler.bundle(); 56 | const sourceMapLocation = global.isProd ? './' : ''; 57 | 58 | return stream 59 | .on('error', handleErrors) 60 | .on('end', bundleLogger.end) 61 | .pipe(source(file)) 62 | .pipe(gulpif(shouldCreateSourcemap, buffer())) 63 | .pipe(gulpif(shouldCreateSourcemap, sourcemaps.init({ loadMaps: true }))) 64 | .pipe(gulpif(global.isProd, streamify(uglify({ 65 | compress: { drop_console: true } // eslint-disable-line camelcase 66 | })))) 67 | .pipe(gulpif(shouldCreateSourcemap, sourcemaps.write(sourceMapLocation))) 68 | .pipe(gulp.dest(config.scripts.dest)) 69 | .pipe(browserSync.stream()); 70 | } 71 | 72 | return rebundle(); 73 | 74 | } 75 | 76 | gulp.task('browserify', function() { 77 | 78 | return buildScript('main.js'); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /gulp/tasks/clean.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | import del from 'del'; 4 | 5 | gulp.task('clean', function() { 6 | 7 | return del([config.buildDir]); 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /gulp/tasks/deploy.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | 3 | gulp.task('deploy', ['prod'], function() { 4 | 5 | // Any deployment logic should go here 6 | 7 | }); -------------------------------------------------------------------------------- /gulp/tasks/development.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import runSequence from 'run-sequence'; 3 | 4 | gulp.task('dev', ['clean'], function(cb) { 5 | 6 | global.isProd = false; 7 | 8 | runSequence(['styles', 'images', 'fonts', 'views'], 'browserify', 'watch', cb); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /gulp/tasks/eslint.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | import eslint from 'gulp-eslint'; 4 | 5 | gulp.task('eslint', () => { 6 | return gulp.src([config.scripts.src, '!app/js/templates.js', config.scripts.test, config.scripts.gulp]) 7 | .pipe(eslint()) 8 | .pipe(eslint.format()) 9 | .pipe(eslint.failAfterError()); 10 | }); 11 | -------------------------------------------------------------------------------- /gulp/tasks/fonts.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import changed from 'gulp-changed'; 3 | import gulp from 'gulp'; 4 | import browserSync from 'browser-sync'; 5 | 6 | gulp.task('fonts', function() { 7 | 8 | return gulp.src(config.fonts.src) 9 | .pipe(changed(config.fonts.dest)) // Ignore unchanged files 10 | .pipe(gulp.dest(config.fonts.dest)) 11 | .pipe(browserSync.stream()); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /gulp/tasks/gzip.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | import gzip from 'gulp-gzip'; 4 | 5 | gulp.task('gzip', function() { 6 | 7 | return gulp.src(config.gzip.src) 8 | .pipe(gzip(config.gzip.options)) 9 | .pipe(gulp.dest(config.gzip.dest)); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /gulp/tasks/images.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import changed from 'gulp-changed'; 3 | import gulp from 'gulp'; 4 | import gulpif from 'gulp-if'; 5 | import imagemin from 'gulp-imagemin'; 6 | import browserSync from 'browser-sync'; 7 | 8 | gulp.task('images', function() { 9 | 10 | return gulp.src(config.images.src) 11 | .pipe(changed(config.images.dest)) // Ignore unchanged files 12 | .pipe(gulpif(global.isProd, imagemin())) // Optimize 13 | .pipe(gulp.dest(config.images.dest)) 14 | .pipe(browserSync.stream()); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /gulp/tasks/production.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import runSequence from 'run-sequence'; 3 | 4 | gulp.task('prod', ['clean'], function(cb) { 5 | 6 | cb = cb || function() {}; 7 | 8 | global.isProd = true; 9 | 10 | runSequence(['styles', 'images', 'fonts', 'views'], 'browserify', 'gzip', cb); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /gulp/tasks/protractor.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import testServer from '../util/testServer'; 3 | import gulp from 'gulp'; 4 | import { 5 | protractor, 6 | webdriver_update, // eslint-disable-line camelcase 7 | webdriver 8 | } from 'gulp-protractor'; 9 | 10 | gulp.task('webdriverUpdate', webdriver_update); 11 | gulp.task('webdriver', webdriver); 12 | 13 | gulp.task('protractor', ['prod', 'webdriverUpdate', 'webdriver'], function(cb) { 14 | 15 | const testFiles = gulp.src('test/e2e/**/*.js'); 16 | 17 | testServer({ 18 | port: config.testPort, 19 | dir: config.buildDir 20 | }).then((server) => { 21 | testFiles.pipe(protractor({ 22 | configFile: config.test.protractor 23 | })).on('error', (err) => { 24 | // Make sure failed tests cause gulp to exit non-zero 25 | throw err; 26 | }).on('end', () => server.close(cb)); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /gulp/tasks/styles.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | import gulpif from 'gulp-if'; 4 | import sourcemaps from 'gulp-sourcemaps'; 5 | import sass from 'gulp-sass'; 6 | import sassGlob from 'gulp-sass-glob'; 7 | import handleErrors from '../util/handleErrors'; 8 | import browserSync from 'browser-sync'; 9 | import autoprefixer from 'gulp-autoprefixer'; 10 | 11 | gulp.task('styles', function () { 12 | 13 | const createSourcemap = !global.isProd || config.styles.prodSourcemap; 14 | 15 | return gulp.src(config.styles.src) 16 | .pipe(gulpif(createSourcemap, sourcemaps.init())) 17 | .pipe(sassGlob()) 18 | .pipe(sass({ 19 | sourceComments: !global.isProd, 20 | outputStyle: global.isProd ? 'compressed' : 'nested', 21 | includePaths: config.styles.sassIncludePaths 22 | })) 23 | .on('error', handleErrors) 24 | .pipe(autoprefixer({ 25 | browsers: ['last 2 versions', '> 1%', 'ie 8'] 26 | })) 27 | .pipe(gulpif( 28 | createSourcemap, 29 | sourcemaps.write( global.isProd ? './' : null )) 30 | ) 31 | .pipe(gulp.dest(config.styles.dest)) 32 | .pipe(browserSync.stream()); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /gulp/tasks/test.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import runSequence from 'run-sequence'; 3 | 4 | gulp.task('test', function() { 5 | 6 | return runSequence('unit', 'protractor'); 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /gulp/tasks/unit.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import path from 'path'; 3 | import gulp from 'gulp'; 4 | import {Server} from 'karma'; 5 | 6 | gulp.task('unit', ['views'], function(cb) { 7 | 8 | new Server({ 9 | configFile: path.resolve(__dirname, '../..', config.test.karma), 10 | singleRun: true 11 | }, cb).start(); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /gulp/tasks/views.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | import merge from 'merge-stream'; 4 | import templateCache from 'gulp-angular-templatecache'; 5 | 6 | // Views task 7 | gulp.task('views', function() { 8 | 9 | // Put our index.html in the dist folder 10 | const indexFile = gulp.src(config.views.index) 11 | .pipe(gulp.dest(config.buildDir)); 12 | 13 | // Process any other view files from app/views 14 | const views = gulp.src(config.views.src) 15 | .pipe(templateCache({ 16 | standalone: true 17 | })) 18 | .pipe(gulp.dest(config.views.dest)); 19 | 20 | return merge(indexFile, views); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /gulp/tasks/watch.js: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import gulp from 'gulp'; 3 | 4 | gulp.task('watch', ['browserSync'], function() { 5 | 6 | global.isWatching = true; 7 | 8 | // Scripts are automatically watched and rebundled by Watchify inside Browserify task 9 | gulp.watch(config.scripts.src, ['eslint']); 10 | gulp.watch(config.styles.src, ['styles']); 11 | gulp.watch(config.images.src, ['images']); 12 | gulp.watch(config.fonts.src, ['fonts']); 13 | gulp.watch(config.views.watch, ['views']); 14 | 15 | }); -------------------------------------------------------------------------------- /gulp/util/bundleLogger.js: -------------------------------------------------------------------------------- 1 | import gutil from 'gulp-util'; 2 | import prettyHrtime from 'pretty-hrtime'; 3 | 4 | let startTime; 5 | 6 | export default { 7 | 8 | start() { 9 | startTime = process.hrtime(); 10 | gutil.log(`${gutil.colors.green('Rebundling')}...`); 11 | }, 12 | 13 | end() { 14 | const taskTime = process.hrtime(startTime); 15 | const prettyTime = prettyHrtime(taskTime); 16 | gutil.log(`Finished ${gutil.colors.green('rebundling')} in ${gutil.colors.magenta(prettyTime)}`); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /gulp/util/handleErrors.js: -------------------------------------------------------------------------------- 1 | import gutil from 'gulp-util'; 2 | import notify from 'gulp-notify'; 3 | 4 | export default function(error) { 5 | 6 | if( !global.isProd ) { 7 | 8 | const args = Array.prototype.slice.call(arguments); 9 | 10 | // Send error to notification center with gulp-notify 11 | notify.onError({ 12 | title: 'Compile Error', 13 | message: '<%= error.message %>' 14 | }).apply(this, args); 15 | 16 | // Keep gulp from hanging on this task 17 | this.emit('end'); 18 | 19 | } else { 20 | // Log the error and stop the process 21 | // to prevent broken code from building 22 | gutil.log(gutil.colors.red(error)); 23 | process.exit(1); 24 | } 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /gulp/util/scriptFilter.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | // Filters out non .js files. Prevents 4 | // accidental inclusion of possible hidden files 5 | export default function(name) { 6 | 7 | return /(\.(js)$)/i.test(path.extname(name)); 8 | 9 | }; -------------------------------------------------------------------------------- /gulp/util/testServer.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | export default function testServer({port, dir}) { 4 | 5 | const app = express(); 6 | 7 | app.use(express.static(dir)); 8 | 9 | return new Promise((resolve) => { 10 | const server = app.listen(port, () => { 11 | resolve(server); 12 | }); 13 | }); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * gulpfile.js 3 | * =========== 4 | * Rather than manage one giant configuration file responsible 5 | * for creating multiple tasks, each task has been broken out into 6 | * its own file in gulp/tasks. Any file in that folder gets automatically 7 | * required by the loop in ./gulp/index.js (required below). 8 | * 9 | * To add a new task, simply add a new task file to gulp/tasks. 10 | */ 11 | 12 | global.isProd = false; 13 | 14 | require('./gulp'); 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-gulp-browserify-boilerplate", 3 | "version": "1.7.1", 4 | "author": "Jake Marsh ", 5 | "description": "Boilerplate using AngularJS, SASS, Gulp, and Browserify while also utilizing best practices.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jakemmarsh/angularjs-gulp-browserify-boilerplate.git" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "express", 13 | "gulp", 14 | "browserify", 15 | "angular", 16 | "sass", 17 | "karma", 18 | "jasmine", 19 | "protractor", 20 | "boilerplate" 21 | ], 22 | "private": false, 23 | "engines": { 24 | "node": "~4.2.x" 25 | }, 26 | "scripts": { 27 | "dev": "cross-env NODE_ENV=development ./node_modules/.bin/gulp dev", 28 | "build": "cross-env NODE_ENV=production ./node_modules/.bin/gulp prod", 29 | "deploy": "cross-env NODE_ENV=production ./node_modules/.bin/gulp deploy", 30 | "test": "cross-env NODE_ENV=test ./node_modules/.bin/gulp test", 31 | "protractor": "cross-env NODE_ENV=test ./node_modules/.bin/gulp protractor", 32 | "unit": "cross-env NODE_ENV=test ./node_modules/.bin/gulp unit" 33 | }, 34 | "dependencies": { 35 | "cross-env": "^3.1.1" 36 | }, 37 | "devDependencies": { 38 | "angular": "^1.5.0", 39 | "angular-mocks": "^1.3.15", 40 | "angular-ui-router": "^0.3.1", 41 | "babel-core": "^6.3.26", 42 | "babel-eslint": "^7.0.0", 43 | "babel-preset-es2015": "^6.3.13", 44 | "babel-register": "^6.5.2", 45 | "babelify": "^7.2.0", 46 | "brfs": "^1.2.0", 47 | "browser-sync": "^2.7.6", 48 | "browserify": "^13.0.0", 49 | "browserify-istanbul": "^2.0.0", 50 | "browserify-ngannotate": "^2.0.0", 51 | "bulk-require": "^1.0.0", 52 | "bulkify": "^1.1.1", 53 | "debowerify": "^1.3.1", 54 | "del": "^2.1.0", 55 | "envify": "^3.4.0", 56 | "eslint": "3.7.1", 57 | "express": "^4.13.3", 58 | "gulp": "^3.9.0", 59 | "gulp-angular-templatecache": "^2.0.0", 60 | "gulp-autoprefixer": "^3.1.0", 61 | "gulp-changed": "^1.0.0", 62 | "gulp-eslint": "^3.0.1", 63 | "gulp-gzip": "^1.2.0", 64 | "gulp-if": "^2.0.0", 65 | "gulp-imagemin": "^3.0.3", 66 | "gulp-notify": "^2.0.0", 67 | "gulp-protractor": "^3.0.0", 68 | "gulp-rename": "^1.2.0", 69 | "gulp-sass": "^2.0.4", 70 | "gulp-sass-glob": "^1.0.6", 71 | "gulp-sourcemaps": "^1.6.0", 72 | "gulp-streamify": "^1.0.2", 73 | "gulp-uglify": "^2.0.0", 74 | "gulp-util": "^3.0.1", 75 | "imagemin-pngcrush": "^5.0.0", 76 | "isparta": "^4.0.0", 77 | "karma": "^1.3.0", 78 | "karma-browserify": "^5.0.2", 79 | "karma-chrome-launcher": "^2.0.0", 80 | "karma-coverage": "douglasduteil/karma-coverage#next", 81 | "karma-firefox-launcher": "^1.0.0", 82 | "karma-jasmine": "^1.0.2", 83 | "karma-sauce-launcher": "^1.0.0", 84 | "merge-stream": "^1.0.0", 85 | "pretty-hrtime": "^1.0.1", 86 | "run-sequence": "^1.1.5", 87 | "tiny-lr": "^0.2.1", 88 | "uglifyify": "^3.0.1", 89 | "vinyl-buffer": "^1.0.0", 90 | "vinyl-source-stream": "^1.1.0", 91 | "watchify": "^3.7.0" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jasmine": true 4 | }, 5 | "globals": { 6 | "angular": true 7 | } 8 | } -------------------------------------------------------------------------------- /test/e2e/example_spec.js: -------------------------------------------------------------------------------- 1 | /*global browser, by */ 2 | 3 | describe('E2E: Example', function() { 4 | 5 | beforeEach(function() { 6 | browser.get('/'); 7 | browser.waitForAngular(); 8 | }); 9 | 10 | it('should route correctly', function() { 11 | expect(browser.getLocationAbsUrl()).toMatch('/'); 12 | }); 13 | 14 | it('should show the number defined in the controller', function() { 15 | var element = browser.findElement(by.css('.number-example')); 16 | expect(element.getText()).toEqual('1234'); 17 | }); 18 | 19 | }); -------------------------------------------------------------------------------- /test/e2e/routes_spec.js: -------------------------------------------------------------------------------- 1 | /*global browser */ 2 | 3 | describe('E2E: Routes', function() { 4 | 5 | it('should have a working home route', function() { 6 | browser.get('#/'); 7 | expect(browser.getLocationAbsUrl()).toMatch('/'); 8 | }); 9 | 10 | }); -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | const istanbul = require('browserify-istanbul'); 2 | const isparta = require('isparta'); 3 | 4 | const karmaBaseConfig = { 5 | 6 | basePath: '../', 7 | 8 | singleRun: true, 9 | 10 | frameworks: ['jasmine', 'browserify'], 11 | 12 | preprocessors: { 13 | 'app/js/**/*.js': ['browserify', 'coverage'], 14 | 'test/**/*.js': ['browserify'] 15 | }, 16 | 17 | browsers: ['Chrome'], 18 | 19 | reporters: ['progress', 'coverage'], 20 | 21 | autoWatch: true, 22 | 23 | browserify: { 24 | debug: true, 25 | extensions: ['.js'], 26 | transform: [ 27 | 'babelify', 28 | 'browserify-ngannotate', 29 | 'bulkify', 30 | istanbul({ 31 | instrumenter: isparta, 32 | ignore: ['**/node_modules/**', '**/test/**'] 33 | }) 34 | ] 35 | }, 36 | 37 | proxies: { 38 | '/': 'http://localhost:9876/' 39 | }, 40 | 41 | urlRoot: '/__karma__/', 42 | 43 | files: [ 44 | // app-specific code 45 | 'app/js/main.js', 46 | 47 | // 3rd-party resources 48 | 'node_modules/angular-mocks/angular-mocks.js', 49 | 50 | // test files 51 | 'test/unit/**/*.js' 52 | ] 53 | 54 | }; 55 | 56 | const customLaunchers = { 57 | chrome: { 58 | base: 'SauceLabs', 59 | browserName: 'chrome' 60 | } 61 | }; 62 | 63 | const ciAdditions = { 64 | sauceLabs: { 65 | testName: 'Karma Unit Tests', 66 | startConnect: false, 67 | build: process.env.TRAVIS_BUILD_NUMBER, 68 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER 69 | }, 70 | browsers: Object.keys(customLaunchers), 71 | customLaunchers: customLaunchers, 72 | reporters: ['progress', 'coverage', 'saucelabs'] 73 | }; 74 | 75 | module.exports = function(config) { 76 | const isCI = process.env.CI && Boolean(process.env.TRAVIS_PULL_REQUEST); 77 | config.set(isCI ? Object.assign(karmaBaseConfig, ciAdditions) : karmaBaseConfig); 78 | }; 79 | -------------------------------------------------------------------------------- /test/protractor.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | const gulpConfig = require('../gulp/config').default; 4 | 5 | exports.config = { 6 | 7 | allScriptsTimeout: 11000, 8 | 9 | baseUrl: `http://localhost:${gulpConfig.testPort}/`, 10 | 11 | capabilities: { 12 | browserName: 'chrome', 13 | 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, 14 | build: process.env.TRAVIS_BUILD_NUMBER, 15 | name: 'Protractor Tests' 16 | }, 17 | 18 | framework: 'jasmine2', 19 | 20 | jasmineNodeOpts: { 21 | isVerbose: false, 22 | showColors: true, 23 | includeStackTrace: true, 24 | defaultTimeoutInterval: 30000 25 | }, 26 | 27 | specs: [ 28 | 'e2e/**/*.js' 29 | ], 30 | 31 | sauceUser: process.env.SAUCE_USERNAME, 32 | 33 | sauceKey: process.env.SAUCE_ACCESS_KEY 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /test/unit/constants_spec.js: -------------------------------------------------------------------------------- 1 | describe('Unit: Constants', function() { 2 | 3 | let constants; 4 | 5 | beforeEach(function() { 6 | // instantiate the app module 7 | angular.mock.module('app'); 8 | 9 | // mock the directive 10 | angular.mock.inject((AppSettings) => { 11 | constants = AppSettings; 12 | }); 13 | }); 14 | 15 | it('should exist', function() { 16 | expect(constants).toBeDefined(); 17 | }); 18 | 19 | it('should have an application name', function() { 20 | expect(constants.appTitle).toEqual('Example Application'); 21 | }); 22 | 23 | }); -------------------------------------------------------------------------------- /test/unit/controllers/example_spec.js: -------------------------------------------------------------------------------- 1 | describe('Unit: ExampleCtrl', function() { 2 | 3 | let ctrl; 4 | 5 | beforeEach(function() { 6 | // instantiate the app module 7 | angular.mock.module('app'); 8 | 9 | angular.mock.inject(($controller) => { 10 | ctrl = $controller('ExampleCtrl'); 11 | }); 12 | }); 13 | 14 | it('should exist', function() { 15 | expect(ctrl).toBeDefined(); 16 | }); 17 | 18 | it('should have a number variable equal to 1234', function() { 19 | expect(ctrl.number).toEqual(1234); 20 | }); 21 | 22 | it('should have a title variable equal to \'AngularJS, Gulp, and Browserify!\'', function() { 23 | expect(ctrl.title).toEqual('AngularJS, Gulp, and Browserify! Written with keyboards and love!'); 24 | }); 25 | 26 | }); -------------------------------------------------------------------------------- /test/unit/directives/example_spec.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | 3 | describe('Unit: ExampleDirective', function() { 4 | 5 | let element; 6 | let scope; 7 | 8 | beforeEach(function() { 9 | spyOn(window, 'alert'); 10 | angular.mock.module('app'); 11 | 12 | angular.mock.inject(($compile, $rootScope) => { 13 | scope = $rootScope; 14 | scope.title = 'A sample title'; 15 | scope.message = 'A sample message'; 16 | 17 | element = angular.element( 18 | '
Sample Directive
' 19 | ); 20 | 21 | $compile(element)(scope); 22 | scope.$digest(); 23 | }); 24 | }); 25 | 26 | it('should bind itself to the element', function() { 27 | element.triggerHandler('click'); 28 | expect(window.alert).toHaveBeenCalledWith(`Element clicked: ${scope.message}`); 29 | }); 30 | 31 | it('should update its bindings', function() { 32 | scope.message = 'A new sample message'; 33 | scope.$digest(); 34 | element.triggerHandler('click'); 35 | expect(window.alert).toHaveBeenCalledWith(`Element clicked: ${scope.message}`); 36 | }); 37 | 38 | it('should bind a title property to its template', function() { 39 | expect(element.find('h1').text()).toBe(`Directive title: ${scope.title}`); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/filters/example_spec.js: -------------------------------------------------------------------------------- 1 | describe('Unit: ExampleFilter', function() { 2 | 3 | let $filter; 4 | 5 | beforeEach(function() { 6 | // instantiate the app module 7 | angular.mock.module('app'); 8 | 9 | // mock the filter 10 | angular.mock.inject((_$filter_) => { 11 | $filter = _$filter_; 12 | }); 13 | }); 14 | 15 | it('should replace the word "keyboard" with "leopard"', function() { 16 | const testString = 'computers are operated by keyboards'; 17 | const resultString = $filter('ExampleFilter')(testString); 18 | 19 | expect(resultString).toEqual('computers are operated by leopards'); 20 | }); 21 | 22 | }); -------------------------------------------------------------------------------- /test/unit/services/example_spec.js: -------------------------------------------------------------------------------- 1 | describe('Unit: ExampleService', function() { 2 | 3 | let http, service; 4 | 5 | beforeEach(function() { 6 | // instantiate the app module 7 | angular.mock.module('app'); 8 | 9 | // mock the service 10 | angular.mock.inject(($httpBackend, ExampleService) => { 11 | http = $httpBackend; 12 | service = ExampleService; 13 | }); 14 | }); 15 | 16 | it('should exist', function() { 17 | expect(service).toBeDefined(); 18 | }); 19 | 20 | it('should retrieve data', function(done) { 21 | http.expect('GET', 'apiPath').respond(201, {data: 1234}); 22 | 23 | service.get().then((result) => { 24 | expect(result).toEqual({data: 1234}); 25 | }).catch((error) => { 26 | expect(error).toBeUndefined(); 27 | }).then(done); 28 | 29 | http.flush(); 30 | }); 31 | }); 32 | --------------------------------------------------------------------------------