Browse Source

Merge pull request #36 from gmetais/build

Front-end build with Grunt for performances. Fixes #11.
Gaël Métais 10 years ago
parent
commit
c3704fa4ff

+ 1 - 1
.gitignore

@@ -1,6 +1,6 @@
 node_modules
 bower_components
-tmp
+.tmp
 .vagrant
 results/*
 coverage

+ 114 - 11
Gruntfile.js

@@ -1,5 +1,8 @@
 module.exports = function(grunt) {
 
+    // Load all grunt modules
+    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
+
     // Tell our Express server that Grunt launched it
     process.env.GRUNTED = true;
 
@@ -18,7 +21,7 @@ module.exports = function(grunt) {
                 cssRouter: function (fontpath) {
                     var pathArray = fontpath.split('/');
                     var fileName = pathArray[pathArray.length - 1];
-                    return '/front/fonts/' + fileName;
+                    return '/fonts/' + fileName;
                 }
             }
         },
@@ -69,19 +72,22 @@ module.exports = function(grunt) {
         },
         clean: {
             tmp: {
-                src: ['tmp']
+                src: ['.tmp']
             },
             dev: {
                 src: ['front/src/css']
             },
             coverage: {
-                src: ['tmp', 'coverage/']
+                src: ['.tmp', 'coverage/']
+            },
+            build: {
+                src: ['front/build']
             }
         },
         copy: {
             beforeCoverage: {
                 files: [
-                    {src: ['bin/server.js'], dest: 'tmp/'}
+                    {src: ['bin/server.js'], dest: '.tmp/'}
                 ]
             },
             coverage: {
@@ -89,12 +95,18 @@ module.exports = function(grunt) {
                     {src: ['test/**'], dest: 'coverage/'},
                     {src: ['lib/metadata/**'], dest: 'coverage/'}
                 ]
+            },
+            build: {
+                files: [
+                    {src: ['./front/src/fonts/icons.woff'], dest: './front/build/fonts/icons.woff'},
+                    {src: ['./front/src/img/favicon.png'], dest: './front/build/img/favicon.png'},
+                ]
             }
         },
         lineremover: {
             beforeCoverage: {
                 files: {
-                    'tmp/bin/cli.js': 'bin/cli.js'
+                    '.tmp/bin/cli.js': 'bin/cli.js'
                 },
                 options: {
                     exclusionPattern: /#!\/usr\/bin\/env node/
@@ -111,7 +123,7 @@ module.exports = function(grunt) {
                 dest: 'coverage/lib/'
             },
             coverageBin: {
-                src: ['tmp/bin/'],
+                src: ['.tmp/bin/'],
                 dest: 'coverage/bin/'
             }
         },
@@ -137,6 +149,14 @@ module.exports = function(grunt) {
                 src: ['coverage/test/core/*.js', 'coverage/test/api/*.js']
             }
         },
+        env: {
+            dev: {
+                NODE_ENV: 'development'
+            },
+            builded: {
+                NODE_ENV: 'production'
+            }
+        },
         express: {
             dev: {
                 options: {
@@ -146,6 +166,14 @@ module.exports = function(grunt) {
                     showStack: true
                 }
             },
+            builded: {
+                options: {
+                    port: 8383,
+                    server: './bin/server.js',
+                    serverreload: true,
+                    showStack: true
+                }
+            },
             test: {
                 options: {
                     port: 8387,
@@ -159,6 +187,67 @@ module.exports = function(grunt) {
                     bases: 'test/www'
                 }
             }
+        },
+        useminPrepare: {
+            html: './front/src/main.html',
+            options: {
+                dest: './front/build',
+                root: ['./', './front/src']
+            }
+        },
+        usemin: {
+            html: './front/build/main.html',
+            css: './front/build/css/*.css',
+            options: {
+                assetsDirs: ['front/build'],
+                patterns: {
+                    css: [[/(\/fonts\/icons\.woff)/gm, 'Replacing reference to icons.woff']]
+                }
+            }
+        },
+        htmlmin: {
+            options: {
+                removeComments: true,
+                collapseWhitespace: true
+            },
+            main: {
+                files: [{
+                    expand: true,
+                    cwd: './front/build/',
+                    src: 'main.html',
+                    flatten: true,
+                    dest: './front/build'
+                }]
+            },
+            views: {
+                files: [{
+                    expand: true,
+                    cwd: './front/src/views',
+                    src: '*.html',
+                    flatten: true,
+                    dest: '.tmp/views/'
+                }]
+            }
+        },
+        inline_angular_templates: {
+            build: {
+                options: {
+                    base: '.tmp',
+                    method: 'append'
+                },
+                files: {
+                    './front/build/main.html': ['.tmp/views/*.html']
+                }
+            }
+        },
+        filerev: {
+            options: {
+                algorithm: 'md5',
+                length: 8
+            },
+            assets: {
+                src: './front/build/*/*.*'
+            }
         }
     });
 
@@ -191,10 +280,6 @@ module.exports = function(grunt) {
     });
 
 
-
-
-    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
-
     grunt.registerTask('icons', [
         'font:icons',
         'less',
@@ -202,8 +287,20 @@ module.exports = function(grunt) {
     ]);
 
     grunt.registerTask('build', [
+        'clean:build',
+        'copy:build',
         'less',
-        'replace'
+        'useminPrepare',
+        'concat',
+        'uglify',
+        'cssmin',
+        'replace',
+        'htmlmin:views',
+        'inline_angular_templates',
+        'filerev',
+        'usemin',
+        'htmlmin:main',
+        'clean:tmp'
     ]);
 
     grunt.registerTask('hint', [
@@ -211,9 +308,15 @@ module.exports = function(grunt) {
     ]);
 
     grunt.registerTask('dev', [
+        'env:dev',
         'express:dev'
     ]);
 
+    grunt.registerTask('builded', [
+        'env:builded',
+        'express:builded'
+    ]);
+
     grunt.registerTask('test', [
         'build',
         'jshint',

+ 1 - 1
front/src/css/icons.css

@@ -1,6 +1,6 @@
 @font-face {
   font-family: "fontsmith-icons";
-  src: url("/front/fonts/icons.woff") format("woff");
+  src: url("/fonts/icons.woff") format("woff");
   font-weight: normal;
   font-style: normal;
 }

+ 1 - 1
front/src/css/main.css

@@ -1,6 +1,6 @@
 @font-face {
   font-family: "fontsmith-icons";
-  src: url("/front/fonts/icons.woff") format("woff");
+  src: url("/fonts/icons.woff") format("woff");
   font-weight: normal;
   font-style: normal;
 }

BIN
front/src/fonts/icons.woff


+ 6 - 6
front/src/js/app.js

@@ -25,27 +25,27 @@ yltApp.config(['$routeProvider', '$locationProvider',
     function($routeProvider, $locationProvider) {
         $routeProvider.
             when('/', {
-                templateUrl: 'front/views/index.html',
+                templateUrl: 'views/index.html',
                 controller: 'IndexCtrl'
             }).
             when('/queue/:runId', {
-                templateUrl: 'front/views/queue.html',
+                templateUrl: 'views/queue.html',
                 controller: 'QueueCtrl'
             }).
             when('/about', {
-                templateUrl: 'front/views/about.html',
+                templateUrl: 'views/about.html',
                 controller: 'AboutCtrl'
             }).
             when('/result/:runId', {
-                templateUrl: 'front/views/dashboard.html',
+                templateUrl: 'views/dashboard.html',
                 controller: 'DashboardCtrl'
             }).
             when('/result/:runId/timeline', {
-                templateUrl: 'front/views/timeline.html',
+                templateUrl: 'views/timeline.html',
                 controller: 'TimelineCtrl'
             }).
             when('/result/:runId/rule/:policy', {
-                templateUrl: 'front/views/rule.html',
+                templateUrl: 'views/rule.html',
                 controller: 'RuleCtrl'
             }).
             otherwise({

+ 2 - 2
front/src/js/directives/gradeDirective.js

@@ -9,7 +9,7 @@ gradeDirective.directive('grade', function() {
         },
         template: '<div ng-class="getGrade(score)">{{getGrade(score)}}</div>',
         replace: true,
-        controller : function($scope) {
+        controller : ['$scope', function($scope) {
             $scope.getGrade = function(score) {
                 if (score > 80) {
                     return 'A';
@@ -28,6 +28,6 @@ gradeDirective.directive('grade', function() {
                 }
                 return 'F';
             };
-        }
+        }]
     };
 });

+ 10 - 10
front/src/less/icons.less

@@ -1,24 +1,24 @@
-@lab-font-family: "fontsmith-icons";
-@lab-value: "\e004";
-@lab: '"fontsmith-icons"' '"\\e004"';
-@list-font-family: "fontsmith-icons";
-@list-value: "\e003";
-@list: '"fontsmith-icons"' '"\\e003"';
-@loop-font-family: "fontsmith-icons";
-@loop-value: "\e002";
-@loop: '"fontsmith-icons"' '"\\e002"';
 @warning-font-family: "fontsmith-icons";
 @warning-value: "\e000";
 @warning: '"fontsmith-icons"' '"\\e000"';
 @question-font-family: "fontsmith-icons";
 @question-value: "\e001";
 @question: '"fontsmith-icons"' '"\\e001"';
+@lab-font-family: "fontsmith-icons";
+@lab-value: "\e004";
+@lab: '"fontsmith-icons"' '"\\e004"';
+@list-font-family: "fontsmith-icons";
+@list-value: "\e003";
+@list: '"fontsmith-icons"' '"\\e003"';
 @bars-font-family: "fontsmith-icons";
 @bars-value: "\e005";
 @bars: '"fontsmith-icons"' '"\\e005"';
 @arrow-left3-font-family: "fontsmith-icons";
 @arrow-left3-value: "\e006";
 @arrow-left3: '"fontsmith-icons"' '"\\e006"';
+@loop-font-family: "fontsmith-icons";
+@loop-value: "\e002";
+@loop: '"fontsmith-icons"' '"\\e002"';
 
 .icon-font-family(@char) {
   font-family: ~`@{char}[0]`;
@@ -49,7 +49,7 @@
 
 @font-face {
   font-family: "fontsmith-icons";
-  src:url("/front/fonts/icons.woff") format("woff"),
+  src:url("/fonts/icons.woff") format("woff"),
     ;
   font-weight: normal;
   font-style: normal;

+ 24 - 26
front/src/main.html

@@ -3,30 +3,34 @@
 	<meta charset="utf-8">
     <title>Yellow Lab Tools</title>
     <base href="/">
-    <link rel="icon" type="image/png" href="/public/img/favicon.png">
+    <link rel="icon" type="image/png" href="/img/favicon.png">
 
-    <link rel="stylesheet" type="text/css" href="/front/css/main.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/index.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/dashboard.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/queue.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/rule.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/timeline.css">
-    <link rel="stylesheet" type="text/css" href="/front/css/about.css">
+    <!-- build:css /css/styles.css-->
+    <link rel="stylesheet" type="text/css" href="/css/main.css">
+    <link rel="stylesheet" type="text/css" href="/css/index.css">
+    <link rel="stylesheet" type="text/css" href="/css/dashboard.css">
+    <link rel="stylesheet" type="text/css" href="/css/queue.css">
+    <link rel="stylesheet" type="text/css" href="/css/rule.css">
+    <link rel="stylesheet" type="text/css" href="/css/timeline.css">
+    <link rel="stylesheet" type="text/css" href="/css/about.css">
+    <!-- endbuild -->
 
+    <!-- build:js /js/all.js -->
     <script src="/bower_components/angular/angular.min.js"></script>
     <script src="/bower_components/angular-route/angular-route.min.js"></script>
     <script src="/bower_components/angular-resource/angular-resource.min.js"></script>
-    <script src="/front/js/app.js"></script>
-    <script src="/front/js/controllers/indexCtrl.js"></script>
-    <script src="/front/js/controllers/aboutCtrl.js"></script>
-    <script src="/front/js/controllers/dashboardCtrl.js"></script>
-    <script src="/front/js/controllers/queueCtrl.js"></script>
-    <script src="/front/js/controllers/ruleCtrl.js"></script>
-    <script src="/front/js/controllers/timelineCtrl.js"></script>
-    <script src="/front/js/models/resultsFactory.js"></script>
-    <script src="/front/js/models/runsFactory.js"></script>
-    <script src="/front/js/services/menuService.js"></script>
-    <script src="/front/js/directives/gradeDirective.js"></script>
+    <script src="/js/app.js"></script>
+    <script src="/js/controllers/indexCtrl.js"></script>
+    <script src="/js/controllers/aboutCtrl.js"></script>
+    <script src="/js/controllers/dashboardCtrl.js"></script>
+    <script src="/js/controllers/queueCtrl.js"></script>
+    <script src="/js/controllers/ruleCtrl.js"></script>
+    <script src="/js/controllers/timelineCtrl.js"></script>
+    <script src="/js/models/resultsFactory.js"></script>
+    <script src="/js/models/runsFactory.js"></script>
+    <script src="/js/services/menuService.js"></script>
+    <script src="/js/directives/gradeDirective.js"></script>
+    <!-- endbuild -->
 <head>
 
 <body ng-app="YellowLabTools">
@@ -38,13 +42,7 @@
     </div>
 
     <script>
-        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-        if ('@@googleAnalyticsId'.indexOf('UA-') === 0) {
-            ga('create', '@@googleAnalyticsId', 'auto');
-        }
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');if('@@googleAnalyticsId'.indexOf('UA-')===0){ga('create','@@googleAnalyticsId','auto');}
     </script>
 </body>
 </html>

+ 3 - 6
front/src/views/dashboard.html

@@ -1,4 +1,4 @@
-<div ng-include="'/front/views/resultSubHeader.html'"></div>
+<div ng-include="'views/resultSubHeader.html'"></div>
 <div class="summary board">
     
     <div class="globalScore" ng-if="globalScore === 0 || globalScore > 0">
@@ -16,11 +16,8 @@
             <div class="category">{{category.label}}</div>
             <div class="criteria">
                 <div class="table" title="Click to see details">
-                    <div ng-repeat="ruleName in category.rules"
-                         ng-if="result.rules[ruleName]"
-                         ng-init="rule = result.rules[ruleName]"
-                         ng-class="{'warning': rule.abnormal}"
-                         ng-click="showRulePage(ruleName)">
+                    <div ng-repeat="ruleName in category.rules" ng-if="result.rules[ruleName]" ng-init="rule = result.rules[ruleName]"
+                         ng-class="{'warning': rule.abnormal}" ng-click="showRulePage(ruleName)">
                         <div class="grade">
                             <grade score="rule.score"></grade>
                         </div>

+ 1 - 3
front/src/views/queue.html

@@ -8,9 +8,7 @@
 </div>
 <div ng-if="status.statusCode == 'awaiting'">
     <div class="status">
-        <ng-pluralize count="status.position" when="{
-                         'one': 'Waiting behind 1 other test',
-                         'other': 'Waiting behind {} other tests'}">
+        <ng-pluralize count="status.position" when="{'one': 'Waiting behind 1 other test', 'other': 'Waiting behind {} other tests'}">
         </ng-pluralize>
     </div>
     <p class="statusSubMessage">(auto-refresh activated)</p>

+ 2 - 5
front/src/views/rule.html

@@ -1,4 +1,4 @@
-<div ng-include="'/front/views/resultSubHeader.html'"></div>
+<div ng-include="'views/resultSubHeader.html'"></div>
 <div class="rule board">
     <div class="backToDashboard"><a href="#" ng-click="backToDashboard()">Back to dashboard</a></div>
 
@@ -19,10 +19,7 @@
     </div>
     <div class="offenders">
         <h3>
-            <ng-pluralize count="rule.offenders.length || 0"
-                 when="{'0': 'No offenders',
-                        'one': '1 offender',
-                        'other': '{} offenders'}">
+            <ng-pluralize count="rule.offenders.length || 0" when="{'0': 'No offenders', 'one': '1 offender', 'other': '{} offenders'}">
             </ng-pluralize>
         </h3>
         <div class="offendersTable">

+ 16 - 21
front/src/views/timeline.html

@@ -1,4 +1,4 @@
-<div ng-include="'/front/views/resultSubHeader.html'"></div>
+<div ng-include="'views/resultSubHeader.html'"></div>
 <div class="execution board">
     <h2>Javascript Timeline</h2>
     <p>This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.</p>
@@ -9,14 +9,14 @@
                 <div ng-repeat="duration in timeline track by $index"
                      class="interval"
                      ng-class="{
-                                domCreation: $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domInteractive,
-                                domInteractive: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domInteractive
-                                                && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoaded,
-                                domContentLoaded: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoaded
-                                                && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoadedEnd,
-                                domContentLoadedEnd: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoadedEnd
-                                                && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domComplete,
-                                domComplete: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domComplete
+                        domCreation: $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domInteractive,
+                        domInteractive: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domInteractive
+                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoaded,
+                        domContentLoaded: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoaded
+                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoadedEnd,
+                        domContentLoadedEnd: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoadedEnd
+                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domComplete,
+                        domComplete: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domComplete
                      }">
                     <div style="height: {{100 * duration / timelineMax | number: 0}}px" class="color"></div>
                     <div class="tooltip detailsOverlay">
@@ -70,14 +70,12 @@
             <div><!-- details --></div>
             <div>Timestamp</div>
         </div>
-        <div ng-repeat="node in profilerData"
-             ng-if="(!warningsFilterOn || node.warning || node.error)
-                     && (!textFilterOn || !textFilter.length || node.searchIndex.indexOf(textFilter) >= 0)"
-             ng-class="{
-                            showingDetails: node.showDetails,
-                            jsError: node.error,
-                            windowPerformance: node.windowPerformance
-                        }">
+        <div ng-if="(!warningsFilterOn || node.warning || node.error) && (!textFilterOn || !textFilter.length || node.searchIndex.indexOf(textFilter) >= 0)"
+             ng-repeat="node in profilerData" ng-class="{
+                showingDetails: node.showDetails,
+                jsError: node.error,
+                windowPerformance: node.windowPerformance
+            }">
 
             <div class="index">{{$index + 1}}</div>
             <div class="type">{{node.data.type}}</div>
@@ -90,10 +88,7 @@
             </div>
             
             <div class="details">
-                <div ng-class="{
-                            'icon-question': !node.warning && !node.error,
-                            'icon-warning': node.warning || node.error
-                        }"
+                <div ng-class="{'icon-question': !node.warning && !node.error, 'icon-warning': node.warning || node.error}"
                      ng-click="onNodeDetailsClick(node)"
                      ng-if="node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance"></div>
                 

+ 13 - 6
lib/server/controllers/frontController.js

@@ -1,19 +1,26 @@
-var path = require('path');
-var express = require('express');
+var path        = require('path');
+var express     = require('express');
 
 var FrontController = function(app) {
     'use strict';
+
+    var cacheDuration = 365 * 24 * 60 * 60 * 1000; // One year
+    var assetsPath = (app.get('env') === 'development') ? '../../../front/src' : '../../../front/build';
     
     var routes = ['/', '/about', '/result/:runId', '/result/:runId/timeline', '/result/:runId/rule/:policy', '/queue/:runId'];
-    
     routes.forEach(function(route) {
         app.get(route, function(req, res) {
-            res.sendFile(path.join(__dirname, '../../../front/src/main.html')); 
+            res.setHeader('Cache-Control', 'public, max-age=20');
+            res.sendFile(path.join(__dirname, assetsPath, 'main.html'));
         });
     });
     
-    app.use('/front', express.static(path.join(__dirname, '../../../front/src')));
-    app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components')));
+    app.use('/css', express.static(path.join(__dirname, assetsPath, 'css'), { maxAge: cacheDuration }));
+    app.use('/fonts', express.static(path.join(__dirname, assetsPath, 'fonts'), { maxAge: cacheDuration }));
+    app.use('/img', express.static(path.join(__dirname, assetsPath, 'img'), { maxAge: cacheDuration }));
+    app.use('/js', express.static(path.join(__dirname, assetsPath, 'js'), { maxAge: cacheDuration }));
+    app.use('/views', express.static(path.join(__dirname, assetsPath, 'views'), { maxAge: cacheDuration }));
+    app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components'), { maxAge: cacheDuration }));
 };
 
 module.exports = FrontController;

+ 8 - 0
package.json

@@ -26,14 +26,22 @@
     "grunt": "^0.4.5",
     "grunt-blanket": "^0.0.8",
     "grunt-contrib-clean": "^0.6.0",
+    "grunt-contrib-concat": "^0.5.0",
     "grunt-contrib-copy": "^0.7.0",
+    "grunt-contrib-cssmin": "^0.11.0",
+    "grunt-contrib-htmlmin": "^0.3.0",
     "grunt-contrib-jshint": "^0.10.0",
     "grunt-contrib-less": "^0.12.0",
+    "grunt-contrib-uglify": "^0.7.0",
+    "grunt-env": "^0.4.2",
     "grunt-express": "^1.4.1",
+    "grunt-filerev": "^2.1.2",
     "grunt-fontsmith": "^0.9.1",
+    "grunt-inline-angular-templates": "^0.1.5",
     "grunt-line-remover": "^0.0.2",
     "grunt-mocha-test": "^0.12.4",
     "grunt-replace": "^0.8.0",
+    "grunt-usemin": "^3.0.0",
     "matchdep": "^0.3.0",
     "mocha": "^2.1.0",
     "phantomjs": "^1.9.13",

+ 2 - 2
server_config/server_install.sh

@@ -21,7 +21,7 @@ sudo chown $USER /space
 cd /space
 git clone https://github.com/gmetais/YellowLabTools.git --branch master
 cd YellowLabTools
-npm install --production
+npm install
 bower install --config.interactive=false --allow-root
 
 # Front-end compilation
@@ -31,4 +31,4 @@ grunt build
 # Start the server
 rm server_config/settings.json
 cp server_config/settings-prod.json server_config/settings.json
-forever start -c "node --stack-size=65500" bin/server.js
+NODE_ENV=production forever start -c "node --stack-size=65500" bin/server.js

+ 7 - 2
server_config/server_update.sh

@@ -12,9 +12,14 @@ git stash pop
 
 # In case something was added in package.json or bower.json
 rm -rf node_modules
-npm install --production
+npm install
 rm -rf bower_components
 bower install --config.interactive=false --allow-root
 
+# Front-end compilation
+rm -rf front/build
+npm install -g grunt
+grunt build
+
 # Restart the server
-forever start -c "node --stack-size=65500" server.js
+NODE_ENV=production forever start -c "node --stack-size=65500" bin/server.js