├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .yo-rc.json ├── Gruntfile.js ├── LICENSE ├── README.md ├── app ├── 404.html ├── favicon.ico ├── images │ └── yeoman.png ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ └── controllers │ │ ├── about.js │ │ └── main.js ├── styles │ └── main.css └── views │ ├── about.html │ └── main.html ├── bower.json ├── package.json ├── sample-template.png └── test ├── .jshintrc ├── karma.conf.js └── spec └── controllers ├── about.js └── main.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /.tmp 4 | /.sass-cache 5 | /bower_components 6 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCamelCaseOrUpperCaseIdentifiers": true, 3 | "requireCapitalizedConstructors": true, 4 | "requireParenthesesAroundIIFE": true, 5 | "validateQuoteMarks": "'" 6 | } 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "esnext": true, 7 | "latedef": true, 8 | "noarg": true, 9 | "node": true, 10 | "strict": true, 11 | "undef": true, 12 | "unused": true, 13 | "globals": { 14 | "angular": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-karma": { 3 | "base-path": "../", 4 | "frameworks": "jasmine", 5 | "browsers": "PhantomJS", 6 | "app-files": "app/scripts/**/*.js", 7 | "files-comments": "bower:js,endbower", 8 | "bower-components-path": "bower_components", 9 | "test-files": "test/mock/**/*.js,test/spec/**/*.js" 10 | } 11 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2016-07-27 using generator-angular 0.15.1 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | grunt.loadNpmTasks('grunt-build-control'); 13 | var pkg = require('./package.json'); 14 | // Time how long tasks take. Can help when optimizing build times 15 | require('time-grunt')(grunt); 16 | // Automatically load required Grunt tasks 17 | require('jit-grunt')(grunt, { 18 | useminPrepare: 'grunt-usemin', 19 | ngtemplates: 'grunt-angular-templates', 20 | cdnify: 'grunt-google-cdn' 21 | }); 22 | 23 | // Configurable paths for the application 24 | var appConfig = { 25 | app: require('./bower.json').appPath || 'app', 26 | dist: 'dist' 27 | }; 28 | 29 | // Define the configuration for all the tasks 30 | grunt.initConfig({ 31 | 32 | // Project settings 33 | yeoman: appConfig, 34 | 35 | // Watches files for changes and runs tasks based on the changed files 36 | watch: { 37 | bower: { 38 | files: ['bower.json'], 39 | tasks: ['wiredep'] 40 | }, 41 | js: { 42 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 43 | tasks: ['newer:jshint:all', 'newer:jscs:all'], 44 | options: { 45 | livereload: '<%= connect.options.livereload %>' 46 | } 47 | }, 48 | jsTest: { 49 | files: ['test/spec/{,*/}*.js'], 50 | tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma'] 51 | }, 52 | styles: { 53 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 54 | tasks: ['newer:copy:styles', 'postcss'] 55 | }, 56 | gruntfile: { 57 | files: ['Gruntfile.js'] 58 | }, 59 | livereload: { 60 | options: { 61 | livereload: '<%= connect.options.livereload %>' 62 | }, 63 | files: [ 64 | '<%= yeoman.app %>/{,*/}*.html', 65 | '.tmp/styles/{,*/}*.css', 66 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 67 | ] 68 | } 69 | }, 70 | 71 | // The actual grunt server settings 72 | connect: { 73 | options: { 74 | port: 9000, 75 | // Change this to '0.0.0.0' to access the server from outside. 76 | hostname: 'localhost', 77 | livereload: 35729 78 | }, 79 | livereload: { 80 | options: { 81 | open: true, 82 | middleware: function (connect) { 83 | return [ 84 | connect.static('.tmp'), 85 | connect().use( 86 | '/bower_components', 87 | connect.static('./bower_components') 88 | ), 89 | connect().use( 90 | '/app/styles', 91 | connect.static('./app/styles') 92 | ), 93 | connect.static(appConfig.app) 94 | ]; 95 | } 96 | } 97 | }, 98 | test: { 99 | options: { 100 | port: 9001, 101 | middleware: function (connect) { 102 | return [ 103 | connect.static('.tmp'), 104 | connect.static('test'), 105 | connect().use( 106 | '/bower_components', 107 | connect.static('./bower_components') 108 | ), 109 | connect.static(appConfig.app) 110 | ]; 111 | } 112 | } 113 | }, 114 | dist: { 115 | options: { 116 | open: true, 117 | base: '<%= yeoman.dist %>' 118 | } 119 | } 120 | }, 121 | 122 | // Make sure there are no obvious mistakes 123 | jshint: { 124 | options: { 125 | jshintrc: '.jshintrc', 126 | reporter: require('jshint-stylish') 127 | }, 128 | all: { 129 | src: [ 130 | 'Gruntfile.js', 131 | '<%= yeoman.app %>/scripts/{,*/}*.js' 132 | ] 133 | }, 134 | test: { 135 | options: { 136 | jshintrc: 'test/.jshintrc' 137 | }, 138 | src: ['test/spec/{,*/}*.js'] 139 | } 140 | }, 141 | 142 | // Make sure code styles are up to par 143 | jscs: { 144 | options: { 145 | config: '.jscsrc', 146 | verbose: true 147 | }, 148 | all: { 149 | src: [ 150 | 'Gruntfile.js', 151 | '<%= yeoman.app %>/scripts/{,*/}*.js' 152 | ] 153 | }, 154 | test: { 155 | src: ['test/spec/{,*/}*.js'] 156 | } 157 | }, 158 | 159 | // Empties folders to start fresh 160 | clean: { 161 | dist: { 162 | files: [{ 163 | dot: true, 164 | src: [ 165 | '.tmp', 166 | '<%= yeoman.dist %>/{,*/}*', 167 | '!<%= yeoman.dist %>/.git{,*/}*' 168 | ] 169 | }] 170 | }, 171 | server: '.tmp' 172 | }, 173 | 174 | // Add vendor prefixed styles 175 | postcss: { 176 | options: { 177 | processors: [ 178 | require('autoprefixer-core')({browsers: ['last 1 version']}) 179 | ] 180 | }, 181 | server: { 182 | options: { 183 | map: true 184 | }, 185 | files: [{ 186 | expand: true, 187 | cwd: '.tmp/styles/', 188 | src: '{,*/}*.css', 189 | dest: '.tmp/styles/' 190 | }] 191 | }, 192 | dist: { 193 | files: [{ 194 | expand: true, 195 | cwd: '.tmp/styles/', 196 | src: '{,*/}*.css', 197 | dest: '.tmp/styles/' 198 | }] 199 | } 200 | }, 201 | 202 | // Automatically inject Bower components into the app 203 | wiredep: { 204 | app: { 205 | src: ['<%= yeoman.app %>/index.html'], 206 | ignorePath: /\.\.\// 207 | }, 208 | test: { 209 | devDependencies: true, 210 | src: '<%= karma.unit.configFile %>', 211 | ignorePath: /\.\.\//, 212 | fileTypes:{ 213 | js: { 214 | block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, 215 | detect: { 216 | js: /'(.*\.js)'/gi 217 | }, 218 | replace: { 219 | js: '\'{{filePath}}\',' 220 | } 221 | } 222 | } 223 | } 224 | }, 225 | 226 | // Renames files for browser caching purposes 227 | filerev: { 228 | dist: { 229 | src: [ 230 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 231 | '<%= yeoman.dist %>/styles/{,*/}*.css', 232 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 233 | '<%= yeoman.dist %>/styles/fonts/*' 234 | ] 235 | } 236 | }, 237 | 238 | // Reads HTML for usemin blocks to enable smart builds that automatically 239 | // concat, minify and revision files. Creates configurations in memory so 240 | // additional tasks can operate on them 241 | useminPrepare: { 242 | html: '<%= yeoman.app %>/index.html', 243 | options: { 244 | dest: '<%= yeoman.dist %>', 245 | flow: { 246 | html: { 247 | steps: { 248 | js: ['concat', 'uglifyjs'], 249 | css: ['cssmin'] 250 | }, 251 | post: {} 252 | } 253 | } 254 | } 255 | }, 256 | 257 | // Performs rewrites based on filerev and the useminPrepare configuration 258 | usemin: { 259 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 260 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 261 | js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'], 262 | options: { 263 | assetsDirs: [ 264 | '<%= yeoman.dist %>', 265 | '<%= yeoman.dist %>/images', 266 | '<%= yeoman.dist %>/styles' 267 | ], 268 | patterns: { 269 | js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']] 270 | } 271 | } 272 | }, 273 | 274 | // The following *-min tasks will produce minified files in the dist folder 275 | // By default, your `index.html`'s will take care of 276 | // minification. These next options are pre-configured if you do not wish 277 | // to use the Usemin blocks. 278 | // cssmin: { 279 | // dist: { 280 | // files: { 281 | // '<%= yeoman.dist %>/styles/main.css': [ 282 | // '.tmp/styles/{,*/}*.css' 283 | // ] 284 | // } 285 | // } 286 | // }, 287 | // uglify: { 288 | // dist: { 289 | // files: { 290 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 291 | // '<%= yeoman.dist %>/scripts/scripts.js' 292 | // ] 293 | // } 294 | // } 295 | // }, 296 | // concat: { 297 | // dist: {} 298 | // }, 299 | 300 | imagemin: { 301 | dist: { 302 | files: [{ 303 | expand: true, 304 | cwd: '<%= yeoman.app %>/images', 305 | src: '{,*/}*.{png,jpg,jpeg,gif}', 306 | dest: '<%= yeoman.dist %>/images' 307 | }] 308 | } 309 | }, 310 | 311 | svgmin: { 312 | dist: { 313 | files: [{ 314 | expand: true, 315 | cwd: '<%= yeoman.app %>/images', 316 | src: '{,*/}*.svg', 317 | dest: '<%= yeoman.dist %>/images' 318 | }] 319 | } 320 | }, 321 | 322 | htmlmin: { 323 | dist: { 324 | options: { 325 | collapseWhitespace: true, 326 | conservativeCollapse: true, 327 | collapseBooleanAttributes: true, 328 | removeCommentsFromCDATA: true 329 | }, 330 | files: [{ 331 | expand: true, 332 | cwd: '<%= yeoman.dist %>', 333 | src: ['*.html'], 334 | dest: '<%= yeoman.dist %>' 335 | }] 336 | } 337 | }, 338 | 339 | ngtemplates: { 340 | dist: { 341 | options: { 342 | module: 'yourProjectAppApp', 343 | htmlmin: '<%= htmlmin.dist.options %>', 344 | usemin: 'scripts/scripts.js' 345 | }, 346 | cwd: '<%= yeoman.app %>', 347 | src: 'views/{,*/}*.html', 348 | dest: '.tmp/templateCache.js' 349 | } 350 | }, 351 | 352 | // ng-annotate tries to make the code safe for minification automatically 353 | // by using the Angular long form for dependency injection. 354 | ngAnnotate: { 355 | dist: { 356 | files: [{ 357 | expand: true, 358 | cwd: '.tmp/concat/scripts', 359 | src: '*.js', 360 | dest: '.tmp/concat/scripts' 361 | }] 362 | } 363 | }, 364 | 365 | // Replace Google CDN references 366 | cdnify: { 367 | dist: { 368 | html: ['<%= yeoman.dist %>/*.html'] 369 | } 370 | }, 371 | 372 | // Copies remaining files to places other tasks can use 373 | copy: { 374 | dist: { 375 | files: [{ 376 | expand: true, 377 | dot: true, 378 | cwd: '<%= yeoman.app %>', 379 | dest: '<%= yeoman.dist %>', 380 | src: [ 381 | '*.{ico,png,txt}', 382 | '*.html', 383 | 'images/{,*/}*.{webp}', 384 | 'styles/fonts/{,*/}*.*' 385 | ] 386 | }, { 387 | expand: true, 388 | cwd: '.tmp/images', 389 | dest: '<%= yeoman.dist %>/images', 390 | src: ['generated/*'] 391 | }, { 392 | expand: true, 393 | cwd: 'bower_components/bootstrap/dist', 394 | src: 'fonts/*', 395 | dest: '<%= yeoman.dist %>' 396 | }] 397 | }, 398 | styles: { 399 | expand: true, 400 | cwd: '<%= yeoman.app %>/styles', 401 | dest: '.tmp/styles/', 402 | src: '{,*/}*.css' 403 | } 404 | }, 405 | 406 | // Run some tasks in parallel to speed up the build process 407 | concurrent: { 408 | server: [ 409 | 'copy:styles' 410 | ], 411 | test: [ 412 | 'copy:styles' 413 | ], 414 | dist: [ 415 | 'copy:styles', 416 | 'imagemin', 417 | 'svgmin' 418 | ] 419 | }, 420 | 421 | buildcontrol: { 422 | options: { 423 | dir: 'dist', 424 | commit: true, 425 | push: true, 426 | message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' 427 | }, 428 | pages: { 429 | options: { 430 | remote: 'git@github.com:shinwang1/Angular-deployment-tutorial.git', 431 | branch: 'gh-pages' 432 | } 433 | }, 434 | heroku: { 435 | options: { 436 | remote: 'git@heroku.com:guarded-hamlet-26569.git', 437 | branch: 'master', 438 | tag: pkg.version 439 | } 440 | }, 441 | local: { 442 | options: { 443 | remote: '../', 444 | branch: 'build' 445 | } 446 | } 447 | }, 448 | 449 | // Test settings 450 | karma: { 451 | unit: { 452 | configFile: 'test/karma.conf.js', 453 | singleRun: true 454 | } 455 | } 456 | }); 457 | 458 | 459 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 460 | if (target === 'dist') { 461 | return grunt.task.run(['build', 'connect:dist:keepalive']); 462 | } 463 | 464 | grunt.task.run([ 465 | 'clean:server', 466 | 'wiredep', 467 | 'concurrent:server', 468 | 'postcss:server', 469 | 'connect:livereload', 470 | 'watch' 471 | ]); 472 | }); 473 | 474 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 475 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 476 | grunt.task.run(['serve:' + target]); 477 | }); 478 | 479 | grunt.registerTask('test', [ 480 | 'clean:server', 481 | 'wiredep', 482 | 'concurrent:test', 483 | 'postcss', 484 | 'connect:test', 485 | 'karma' 486 | ]); 487 | 488 | grunt.registerTask('build', [ 489 | 'clean:dist', 490 | 'wiredep', 491 | 'useminPrepare', 492 | 'concurrent:dist', 493 | 'postcss', 494 | 'ngtemplates', 495 | 'concat', 496 | 'ngAnnotate', 497 | 'copy:dist', 498 | 'cdnify', 499 | 'cssmin', 500 | 'uglify', 501 | 'filerev', 502 | 'usemin', 503 | 'htmlmin' 504 | ]); 505 | 506 | grunt.registerTask('default', [ 507 | 'newer:jshint', 508 | 'newer:jscs', 509 | 'test', 510 | 'build' 511 | ]); 512 | }; 513 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Shin 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 | # Angular-deployment-tutorial 2 | macOS, Yeoman, AngularJS 1.x, Grunt, Github, Heroku 3 | 4 | **Purpose:** The purpose for writing this post is to share what I have learned over the three days (through trial and error) about using [Grunt](http://gruntjs.com). I hope that it’ll get you up and running within the hour. I will also be editing to this article as I learn more from the community and documentations. 5 | 6 | **Context:** My team and I had seven days to learn enough about a new framework to build our final project at [Dev Bootcamp](http://devbootcamp.com), a coding bootcamp. We decided to use an AngularJS front-end that connected to our Rails API back-end. While it was easy to deploy our Rails API to Heroku, we were unsuccessful at deploying our front-end. Since we used [Yeoman](http://yeoman.io/) to scaffold our Angular template, the Yeoman system included Grunt. Grunt is essentially a task runner that automates tasks that you would do repeatedly. Since we had little time to learn about how Grunt worked, we did not successfully deploy our Web App to Heroku at the time. Since then, our Web App, UpLift, has been deployed to both Github pages and Heroku. The lesson that we learned is to deploy early and often. Here are the steps that I’ve compiled since that project. Think of this as building an Angular template Web App from scratch. 7 | 8 | ## Project goal: 9 | ![Angular Template](./sample-template.png "Angular Template") 10 | 11 | Here is what the deployed result looks like on gh-pages: 12 | [yourProjectApp](http://shinwang.us/Angular-deployment-tutorial/#/) 13 | 14 | ## Setup and steps on macOS: 15 | 16 | I will use the **$** to represent the mac terminal prompt and **< >** as placeholder for your project's name. 17 | 18 | * Install Node.js to your system by downloading [NodeJS](https://nodejs.org/en) 19 | * To check if you already have a version of node: 20 | ``` 21 | $ node -v 22 | ``` 23 | * Since [node package manager](https://www.npmjs.com/) (npm) comes with the node install, all you’ll have to do is to do is to make sure that it’s up-to-date: 24 | ``` 25 | $ npm install npm -g 26 | ``` 27 | * To install Yeoman: 28 | ``` 29 | $ npm install -g yo 30 | ``` 31 | * To install the Angular generator: 32 | ``` 33 | $ npm install -g generator-angular 34 | ``` 35 | * Make your project folder and change into said project folder: 36 | ``` 37 | $ mkdir -app && cd $_ 38 | ``` 39 | * Track your files with Git 40 | ``` 41 | $ git init 42 | ``` 43 | * Add Github remote 44 | ``` 45 | $ git remote add origin 46 | ``` 47 | * Add, commit, and push to Github 48 | * Inside your project folder: 49 | ``` 50 | $ yo angular -app 51 | ``` 52 | * At this point, Yeoman will walk you through a basic setup prior to scaffolding your project. For my setup, I said **no** to Gulp, **no** to Sass/compass, and **yes** to Bootstrap. After that, I used the default Angular modules. 53 | * If you wish to use [Karma](https://www.npmjs.com/package/grunt-karma) with Grunt to run tests: 54 | ``` 55 | $ npm install grunt-karma --save-dev 56 | ``` 57 | * To see what the scaffolded your-project-app looks like on your localhost, type: 58 | ``` 59 | $ grunt serve 60 | ``` 61 | * At this point, I would recommend that you spend some time looking through the **Gruntfile.js** in the **root of your project folder** to see all of the available tasks that you can type into your command line. For example, ```$ grunt test``` will run tests that you would write under the **test/spec** folder and ```$ grunt``` will minify (one of several tasks) a copied version of your files and place those files into a **dist** (distribution) folder. The advantage of having these automation task commands is that you can continuous build and test your Web App locally and deploy them quickly to check for matching functionality. 62 | 63 | ## Onto deployment 64 | 65 | * Within your project folder: 66 | ``` 67 | $ npm install grunt-build-control --save-dev 68 | ``` 69 | * Then navigate to the [grunt-build-control](https://github.com/robwierzbowski/grunt-build-control) Github page, and use those instructions to complete your **Gruntfile.js**. I will outline what I’ve used here. 70 | * Paste the following into your **Gruntfile.js**. 71 | ``` 72 | grunt.loadNpmTasks(‘grunt-build-control’); 73 | ``` 74 | * Next, refer to the **Usage** section on the grunt-build-control Github page and copy the example tasks into your **Gruntfile.js**. Below is my **Gruntfile.js** at this step of the tutorial. Do note that you’ll have to insert this block of tasks and make sure that each task is separated by a comma. This means that the task preceding ```buildcontrol: {…}``` would require a comma. I've indicated the two places insert code with ```///\```. 75 | ``` 76 | 'use strict'; 77 | 78 | module.exports = function (grunt) { 79 | 80 | grunt.loadNpmTasks(‘grunt-build-control’); ///\<---INSERT HERE 81 | var pkg = require('./package.json'); ///\<---INSERT HERE 82 | 83 | // Time how long tasks take. Can help when optimizing build times 84 | require('time-grunt')(grunt); 85 | 86 | // Automatically load required Grunt tasks 87 | require('jit-grunt')(grunt, { 88 | useminPrepare: 'grunt-usemin', 89 | ngtemplates: 'grunt-angular-templates', 90 | cdnify: 'grunt-google-cdn' 91 | }); 92 | 93 | // Configurable paths for the application 94 | var appConfig = { 95 | app: require('./bower.json').appPath || 'app', 96 | dist: 'dist' 97 | }; 98 | 99 | // Define the configuration for all the tasks 100 | grunt.initConfig({ 101 | 102 | // Project settings 103 | yeoman: appConfig, 104 | 105 | ...// I've hidden the code in-between for brevity 106 | 107 | // Run some tasks in parallel to speed up the build process 108 | concurrent: { 109 | ...// I've hidden the code in-between for brevity 110 | dist: [ 111 | 'copy:styles', 112 | 'imagemin', 113 | 'svgmin' 114 | ] 115 | }, ///\ Don't forget the comma here, the code below is what I've copied over from grunt buildcontrol 116 | 117 | buildcontrol: { 118 | options: { 119 | dir: 'dist', 120 | commit: true, 121 | push: true, 122 | message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' 123 | }, 124 | pages: { 125 | options: { 126 | remote: 'git@github.com:example_user/example_webapp.git', 127 | branch: 'gh-pages' 128 | } 129 | }, 130 | heroku: { 131 | options: { 132 | remote: 'git@heroku.com:example-heroku-webapp-1988.git', 133 | branch: 'master', 134 | tag: pkg.version 135 | } 136 | }, 137 | local: { 138 | options: { 139 | remote: '../', 140 | branch: 'build' 141 | } 142 | } 143 | }, ///\ Don't forget the comma here. The grunt buildcontrol pasted code stops here. 144 | 145 | // Test settings 146 | karma: { 147 | unit: { 148 | configFile: 'test/karma.conf.js', 149 | singleRun: true 150 | } 151 | } 152 | }); 153 | 154 | 155 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 156 | ...// I've hidden the code in-between for brevity 157 | }); 158 | 159 | ...// I've hidden the code in-between for brevity 160 | }; 161 | 162 | ``` 163 | * You will notice that the code block that you’ve just added has remote: ```'git@github.com:example_webapp.git’``` as the filler repository. You will need to manually change the ```example_webapp.git``` to your remote repository name on Github. Here is mine: 164 | ``` 165 | 'git@github.com:Angular-deployment-tutorial.git' 166 | ``` 167 | * Add, commit, and push your project using Git. 168 | * To create a distribution folder of your entire project, run: 169 | ``` 170 | $ grunt build 171 | ``` 172 | * You will notice that a dist folder has been created and is listed on your **.ignore** file. This folder will not be pushed to Github. 173 | * To publish this project to your Github’s gh-pages, run: 174 | ``` 175 | $ grunt buildcontrol:pages 176 | ``` 177 | * A **gh-pages** branch will be created for you inside the dist folder. 178 | * Congrats, you can now visit your deployed Angular template App on 179 | ``` 180 | http://.github.io/ 181 | ``` 182 | * If you wish to deploy this template App to Heroku, there will be a few more steps. 183 | 184 | ## Deployment to Heroku 185 | 186 | * [Install Heroku Toolbelt](https://toolbelt.heroku.com/) 187 | * Log into Heroku from your project directory 188 | ``` 189 | $ heroku login 190 | Enter your Heroku credentials. 191 | Email: you@example.com 192 | Password (typing will be hidden): 193 | Authentication successful. 194 | ``` 195 | * Create your Heroku App: 196 | ``` 197 | $ heroku create 198 | Creating stark-fog-398... done, stack is cedar-14 199 | http://stark-fog-398.herokuapp.com/ | https://git.heroku.com/stark-fog-398.git 200 | Git remote heroku added 201 | ``` 202 | * In the example above, the App, ```stark-fog-398``` has been created on heroku. Your App will have a randomly generated name. Take this name and place it into your **Gruntfile.js**. Here is what I have: 203 | ``` 204 | heroku: { 205 | options: { 206 | remote: 'git@heroku.com:guarded-hamlet-26569.git', ///\INSERT App name 207 | branch: 'master', 208 | tag: pkg.version 209 | } 210 | }, 211 | ``` 212 | * If you want to give your Heroku App a name, then type: 213 | ``` 214 | $ heroku create 215 | ``` 216 | * Change into your dist directory and create the following files: 217 | ``` 218 | $ touch package.json server.js Procfile 219 | ``` 220 | * Copy over your the contents of your **package.json** file in your project root. Change the node versoin to **0.12.0** from **0.10.0**. Leave only the following: 221 | ``` 222 | { 223 | "name": "yourprojectapp", 224 | "engines": { 225 | "node": ">=0.12.0" 226 | }, 227 | "scripts": { 228 | "test": "karma start test/karma.conf.js" 229 | } 230 | } 231 | ``` 232 | * Inside the dist directory, install express: 233 | ``` 234 | $ npm install express --save 235 | ``` 236 | * Your **package.json** will now resemble this: 237 | ``` 238 | { 239 | "name": "yourprojectapp", 240 | "engines": { 241 | "node": ">=0.12.0" 242 | }, 243 | "scripts": { 244 | "test": "karma start test/karma.conf.js" 245 | }, 246 | "dependencies": { 247 | "express": "^4.14.0" 248 | } 249 | } 250 | ``` 251 | * Create a simple express server inside the dist directory. Here is what my server.js looks like: 252 | ``` 253 | var express = require('express'); 254 | 255 | var app = express(); 256 | 257 | app.use(express.static(__dirname)); 258 | 259 | var port = process.env.PORT || 3000; 260 | 261 | app.listen(port); 262 | console.log('listening on ' + port); 263 | ``` 264 | * Heroku requires a Procfile to start the express server. Here is what my Procfile looks like: 265 | ``` 266 | web: node server.js 267 | ``` 268 | * Git add and commit changes for dist directory and project root directory. 269 | * Push to Heroku using Grunt from the project root directory 270 | ``` 271 | $ grunt buildcontrol:heroku 272 | ``` 273 | * If all goes well, then congrats! You are now deployed on both gh-pages and Heroku. 274 | * Here is my final result [yourProjectApp](https://guarded-hamlet-26569.herokuapp.com/#/) 275 | * From here, as you continue to develop your App, you will have to run ```grunt``` to repackage your dist folder. Note that your **Package.json**, **server.js**, and **Procfile** will be deleted in the process. Be sure to copy their contents to a separate file in the root directory before running ```grunt``` from your project root. You will also have to ```npm install express`` from the dist directory. 276 | * With anything, there's probably a better way to automate using Grunt. If would like to add anything, feel free to make a pull request. 277 | 278 | Thanks! 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 136 | 137 | 138 |
139 |

Not found :(

140 |

Sorry, but the page you were trying to view does not exist.

141 |

It looks like this was the result of either:

142 |
    143 |
  • a mistyped address
  • 144 |
  • an out-of-date link
  • 145 |
146 | 149 | 150 |
151 | 152 | 153 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinwang1/Angular-deployment-tutorial/8012734527d0b57e06baf5ba3ce2f8492457a493/app/favicon.ico -------------------------------------------------------------------------------- /app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinwang1/Angular-deployment-tutorial/8012734527d0b57e06baf5ba3ce2f8492457a493/app/images/yeoman.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 |
25 | 49 |
50 | 51 |
52 |
53 |
54 | 55 | 60 | 61 | 62 | 63 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name yourProjectAppApp 6 | * @description 7 | * # yourProjectAppApp 8 | * 9 | * Main module of the application. 10 | */ 11 | angular 12 | .module('yourProjectAppApp', [ 13 | 'ngAnimate', 14 | 'ngCookies', 15 | 'ngResource', 16 | 'ngRoute', 17 | 'ngSanitize', 18 | 'ngTouch' 19 | ]) 20 | .config(function ($routeProvider) { 21 | $routeProvider 22 | .when('/', { 23 | templateUrl: 'views/main.html', 24 | controller: 'MainCtrl', 25 | controllerAs: 'main' 26 | }) 27 | .when('/about', { 28 | templateUrl: 'views/about.html', 29 | controller: 'AboutCtrl', 30 | controllerAs: 'about' 31 | }) 32 | .otherwise({ 33 | redirectTo: '/' 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /app/scripts/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yourProjectAppApp.controller:AboutCtrl 6 | * @description 7 | * # AboutCtrl 8 | * Controller of the yourProjectAppApp 9 | */ 10 | angular.module('yourProjectAppApp') 11 | .controller('AboutCtrl', function () { 12 | this.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name yourProjectAppApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the yourProjectAppApp 9 | */ 10 | angular.module('yourProjectAppApp') 11 | .controller('MainCtrl', function () { 12 | this.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | .browsehappy { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | body { 9 | padding: 0; 10 | } 11 | 12 | /* Everything but the jumbotron gets side spacing for mobile first views */ 13 | .header, 14 | .marketing, 15 | .footer { 16 | padding-left: 15px; 17 | padding-right: 15px; 18 | } 19 | 20 | /* Custom page header */ 21 | .header { 22 | border-bottom: 1px solid #e5e5e5; 23 | margin-bottom: 10px; 24 | } 25 | /* Make the masthead heading the same height as the navigation */ 26 | .header h3 { 27 | margin-top: 0; 28 | margin-bottom: 0; 29 | line-height: 40px; 30 | padding-bottom: 19px; 31 | } 32 | 33 | /* Custom page footer */ 34 | .footer { 35 | padding-top: 19px; 36 | color: #777; 37 | border-top: 1px solid #e5e5e5; 38 | } 39 | 40 | .container-narrow > hr { 41 | margin: 30px 0; 42 | } 43 | 44 | /* Main marketing message and sign up button */ 45 | .jumbotron { 46 | text-align: center; 47 | border-bottom: 1px solid #e5e5e5; 48 | } 49 | .jumbotron .btn { 50 | font-size: 21px; 51 | padding: 14px 24px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | .marketing p + h4 { 59 | margin-top: 28px; 60 | } 61 | 62 | /* Responsive: Portrait tablets and up */ 63 | @media screen and (min-width: 768px) { 64 | .container { 65 | max-width: 730px; 66 | } 67 | 68 | /* Remove the padding we set earlier */ 69 | .header, 70 | .marketing, 71 | .footer { 72 | padding-left: 0; 73 | padding-right: 0; 74 | } 75 | /* Space out the masthead */ 76 | .header { 77 | margin-bottom: 30px; 78 | } 79 | /* Remove the bottom border on the jumbotron for visual effect */ 80 | .jumbotron { 81 | border-bottom: 0; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 |

This is the about view.

2 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |

'Allo, 'Allo!

3 |

4 | I'm Yeoman
5 | Always a pleasure scaffolding your apps. 6 |

7 |

Splendid!

8 |
9 | 10 |
11 |

HTML5 Boilerplate

12 |

13 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. 14 |

15 | 16 |

Angular

17 |

18 | AngularJS is a toolset for building the framework most suited to your application development. 19 |

20 | 21 |

Karma

22 |

Spectacular Test Runner for JavaScript.

23 |
24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "your-project-app", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "^1.4.0", 6 | "bootstrap": "^3.2.0", 7 | "angular-animate": "^1.4.0", 8 | "angular-cookies": "^1.4.0", 9 | "angular-resource": "^1.4.0", 10 | "angular-route": "^1.4.0", 11 | "angular-sanitize": "^1.4.0", 12 | "angular-touch": "^1.4.0" 13 | }, 14 | "devDependencies": { 15 | "angular-mocks": "^1.4.0" 16 | }, 17 | "appPath": "app", 18 | "moduleName": "yourProjectAppApp", 19 | "overrides": { 20 | "bootstrap": { 21 | "main": [ 22 | "less/bootstrap.less", 23 | "dist/css/bootstrap.css", 24 | "dist/js/bootstrap.js" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yourprojectapp", 3 | "private": true, 4 | "devDependencies": { 5 | "autoprefixer-core": "^5.2.1", 6 | "grunt": "^0.4.5", 7 | "grunt-angular-templates": "^0.5.7", 8 | "grunt-build-control": "^0.7.0", 9 | "grunt-concurrent": "^1.0.0", 10 | "grunt-contrib-clean": "^0.6.0", 11 | "grunt-contrib-concat": "^0.5.0", 12 | "grunt-contrib-connect": "^0.9.0", 13 | "grunt-contrib-copy": "^0.7.0", 14 | "grunt-contrib-cssmin": "^0.12.0", 15 | "grunt-contrib-htmlmin": "^0.4.0", 16 | "grunt-contrib-imagemin": "^1.0.0", 17 | "grunt-contrib-jshint": "^0.11.0", 18 | "grunt-contrib-uglify": "^0.7.0", 19 | "grunt-contrib-watch": "^0.6.1", 20 | "grunt-filerev": "^2.1.2", 21 | "grunt-google-cdn": "^0.4.3", 22 | "grunt-jscs": "^1.8.0", 23 | "grunt-karma": "^2.0.0", 24 | "grunt-newer": "^1.1.0", 25 | "grunt-ng-annotate": "^0.9.2", 26 | "grunt-postcss": "^0.5.5", 27 | "grunt-svgmin": "^2.0.0", 28 | "grunt-usemin": "^3.0.0", 29 | "grunt-wiredep": "^2.0.0", 30 | "jasmine-core": "^2.4.1", 31 | "jit-grunt": "^0.9.1", 32 | "jshint-stylish": "^1.0.0", 33 | "karma": "^1.1.2", 34 | "karma-jasmine": "^1.0.2", 35 | "karma-phantomjs-launcher": "^1.0.1", 36 | "phantomjs-prebuilt": "^2.1.8", 37 | "time-grunt": "^1.0.0" 38 | }, 39 | "engines": { 40 | "node": ">=0.10.0" 41 | }, 42 | "scripts": { 43 | "test": "karma start test/karma.conf.js" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinwang1/Angular-deployment-tutorial/8012734527d0b57e06baf5ba3ce2f8492457a493/sample-template.png -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "esnext": true, 7 | "jasmine": true, 8 | "latedef": true, 9 | "noarg": true, 10 | "node": true, 11 | "strict": true, 12 | "undef": true, 13 | "unused": true, 14 | "globals": { 15 | "angular": false, 16 | "inject": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on 2016-07-27 3 | 4 | module.exports = function(config) { 5 | 'use strict'; 6 | 7 | config.set({ 8 | // enable / disable watching file and executing tests whenever any file changes 9 | autoWatch: true, 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: '../', 13 | 14 | // testing framework to use (jasmine/mocha/qunit/...) 15 | // as well as any additional frameworks (requirejs/chai/sinon/...) 16 | frameworks: [ 17 | 'jasmine' 18 | ], 19 | 20 | // list of files / patterns to load in the browser 21 | files: [ 22 | // bower:js 23 | 'bower_components/jquery/dist/jquery.js', 24 | 'bower_components/angular/angular.js', 25 | 'bower_components/bootstrap/dist/js/bootstrap.js', 26 | 'bower_components/angular-animate/angular-animate.js', 27 | 'bower_components/angular-cookies/angular-cookies.js', 28 | 'bower_components/angular-resource/angular-resource.js', 29 | 'bower_components/angular-route/angular-route.js', 30 | 'bower_components/angular-sanitize/angular-sanitize.js', 31 | 'bower_components/angular-touch/angular-touch.js', 32 | 'bower_components/angular-mocks/angular-mocks.js', 33 | // endbower 34 | 'app/scripts/**/*.js', 35 | 'test/mock/**/*.js', 36 | 'test/spec/**/*.js' 37 | ], 38 | 39 | // list of files / patterns to exclude 40 | exclude: [ 41 | ], 42 | 43 | // web server port 44 | port: 8080, 45 | 46 | // Start these browsers, currently available: 47 | // - Chrome 48 | // - ChromeCanary 49 | // - Firefox 50 | // - Opera 51 | // - Safari (only Mac) 52 | // - PhantomJS 53 | // - IE (only Windows) 54 | browsers: [ 55 | 'PhantomJS' 56 | ], 57 | 58 | // Which plugins to enable 59 | plugins: [ 60 | 'karma-phantomjs-launcher', 61 | 'karma-jasmine' 62 | ], 63 | 64 | // Continuous Integration mode 65 | // if true, it capture browsers, run tests and exit 66 | singleRun: false, 67 | 68 | colors: true, 69 | 70 | // level of logging 71 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 72 | logLevel: config.LOG_INFO, 73 | 74 | // Uncomment the following lines if you are using grunt's server to run the tests 75 | // proxies: { 76 | // '/': 'http://localhost:9000/' 77 | // }, 78 | // URL root prevent conflicts with the site root 79 | // urlRoot: '_karma_' 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yourProjectAppApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 15 | $scope: scope 16 | // place here mocked dependencies 17 | }); 18 | })); 19 | 20 | it('should attach a list of awesomeThings to the scope', function () { 21 | expect(AboutCtrl.awesomeThings.length).toBe(3); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('yourProjectAppApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | // place here mocked dependencies 17 | }); 18 | })); 19 | 20 | it('should attach a list of awesomeThings to the scope', function () { 21 | expect(MainCtrl.awesomeThings.length).toBe(3); 22 | }); 23 | }); 24 | --------------------------------------------------------------------------------