gaf-mobile
Version:
GAF mobile Web site
894 lines (830 loc) • 22.9 kB
JavaScript
// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'
module.exports = function(grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);
// For generating service worker
var swPrecache = require('sw-precache'),
path = require('path');
// Rewrite middleware to get pushstate working in dev mode
var modRewrite = require('connect-modrewrite');
var distDir = 'www';
// Define the configuration for all the tasks
grunt.initConfig({
// Project settings
yeoman: {
// configurable paths
app: require('./bower.json').appPath || 'app',
dist: distDir
},
// Watches files for changes and runs tasks based on the changed files
watch: {
js: {
files: [
'<%= yeoman.app %>/**/*.js',
'fl-angular/src/**/*.js'
],
tasks: ['newer:jshint:all'],
options: {
livereload: true
}
},
less: {
files: [
'<%= yeoman.app %>/styles/**/*.less',
'<%= yeoman.app %>/components/**/*.less'
],
tasks: ['less']
},
gruntfile: {
files: ['Gruntfile.js']
},
ngconstant: {
files: ['config/*.json'],
tasks: ['ngconstant']
},
bower: {
files: ['bower.json'],
tasks: ['exec:bowerInstall']
},
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'<%= yeoman.app %>/**/*.html',
'.tmp/styles/**/*.css',
'<%= yeoman.app %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
]
},
ngdocs: {
files: [
'app/scripts/**/*.js',
'app/components/**/*.js',
'README.md'
],
tasks: ['build-docs'],
options: {
livereload: true
}
},
patternLibrary: {
files: [
'pattern-library/components/**/*.html',
'pattern-library/components/**/*.md',
'pattern-library/**/*.html'
],
tasks: ['build-kss'],
options: {
livereload: true
}
}
},
// The actual grunt server settings
connect: {
options: {
port: 9000,
livereload: 35729,
hostname: '*'
},
livereload: {
options: {
open: grunt.option('open') ?
'http://m-local.freelancer.com:9000' : false,
base: [
'.tmp',
'<%= yeoman.app %>'
],
middleware: function(connect, options) {
var middlewares = [];
middlewares.push(modRewrite([
'^\/(?!bower_components|favicon\.ico|service-worker\.js|manifest\.json|apple-app-site-association|images|scripts|styles|views|components|translations).*$ /index.html [L]' // jshint ignore:line
]));
options.base.forEach(function(base) {
middlewares.push(connect.static(base));
});
return middlewares;
}
}
},
dist: {
options: {
port: 9000,
open: grunt.option('open') ?
'http://m-local.freelancer.com:9000' : false,
base: '<%= yeoman.dist %>',
middleware: function(connect, options) {
var middlewares = [];
middlewares.push(modRewrite([
'^\/(?!bower_components|favicon\.ico|service-worker\.js|manifest\.json|apple-app-site-association|images|scripts|styles|support|views|components|translations).*$ /index.html [L]' // jshint ignore:line
]));
middlewares.push(connect.static(options.base));
return middlewares;
}
}
},
test: {
options: {
port: 9001,
base: [
'.tmp',
'test',
'<%= yeoman.app %>'
]
}
},
ngdocs: {
options: {
port: 1111,
base: 'docs/',
open: true
}
},
patternLibrary: {
options: {
port: 1112,
base: 'pattern-library/dest/',
open: true
}
}
},
// Make sure code styles are up to par and there are no obvious mistakes
jshint: {
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
},
src: [
'Gruntfile.js',
'<%= yeoman.app %>/scripts/**/*.js',
'!<%= yeoman.app %>/scripts/ext/**/*.js',
'<%= yeoman.app %>/components/**/*.js'
]
},
// Empties folders to start fresh
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= yeoman.dist %>/*',
'!<%= yeoman.dist %>/.git*'
]
}]
},
server: '.tmp',
docs: 'docs',
coverage: 'test/coverage'
},
// Add vendor prefixed styles
autoprefixer: {
options: {
browsers: ['last 2 versions']
},
dist: {
files: [{
expand: true,
cwd: '.tmp/styles/',
src: '{,*/}*.css',
dest: '.tmp/styles/'
}]
}
},
// Automatically inject Bower components into the app, i.e. fill
// the bower:js tags inside index.html
wiredep: {
app: {
src: '<%= yeoman.app %>/index.html',
options: {
includeSelf: true,
ignorePath: '<%= yeoman.app %>/'
}
}
},
// Renames files for browser caching purposes
rev: {
dist: {
files: {
src: [
'<%= yeoman.dist %>/scripts/**/*.js',
'<%= yeoman.dist %>/components/**/*.js',
'<%= yeoman.dist %>/styles/**/*.{css,eot,ttf,woff}',
'<%= yeoman.dist %>/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
'<%= yeoman.dist %>/components/**/*.{png,jpg,jpeg,gif,webp,svg}'
]
}
}
},
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
// Default flow: 'js': ['concat', 'uglifyjs'], 'css': ['concat', 'cssmin']
useminPrepare: {
html: '<%= yeoman.app %>/**/*.html',
options: {
dest: '<%= yeoman.dist %>'
}
},
// Generate source maps
uglify: {
options: {
sourceMap: true,
sourceMapIncludeSources: true,
sourceMapLocation: 'https://m.freelancer.com/',
sourceMapRoot: '<%= yeoman.dist %>'
}
},
// Performs rewrites based on rev and the useminPrepare configuration
usemin: {
html: ['<%= yeoman.dist %>/**/*.html'],
htmlflSvg: ['<%= yeoman.dist %>/**/*.html'],
htmli18nSrc: ['<%= yeoman.dist %>/**/*.html'],
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
json: ['<%= yeoman.dist %>/**/*.json'],
options: {
assetsDirs: ['<%= yeoman.dist %>', '<%= yeoman.dist %>/images'],
patterns: {
htmlflSvg: [
[/<fl-svg[^\>]*[^\>\S]+src=['"]([^'"\)#]+)(#.+)?["']/gm,
'Replacing fl-svg references']
],
htmli18nSrc: [
[/<img[^\>]*[^\>\S]+i18n-src=['"]([^'"\)#]+)(#.+)?["']/gm,
'Replacing i18n-src references']
]
}
}
},
// Lossless compression of image files
imagemin: {
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.app %>/',
src: [
'images/**/*.{png,jpg,jpeg,gif}',
'components/**/*.{png,jpg,jpeg,gif}'
],
dest: '<%= yeoman.dist %>/'
}]
}
},
htmlmin: {
dist: {
options: {
removeComments: true,
collapseWhitespace: true,
conservativeCollapse: true,
collapseBooleanAttributes: true,
removeCommentsFromCDATA: true,
minifyJS: true,
keepClosingSlash: true, // needed for inline SVG
processScripts: ['text/ng-template']
},
files: [{
expand: true,
cwd: '<%= yeoman.dist %>',
src: [
'*.html', 'views/**/*.html',
'components/**/*.html'
],
dest: '<%= yeoman.dist %>'
}]
}
},
// Show compression report after CSS minification by usemin
cssmin: {
options: {
report: 'min'
}
},
// Allow the use of non-minsafe AngularJS files. Automatically makes it
// minsafe compatible so Uglify does not destroy the ng references
ngAnnotate: {
dist: {
files: [{
expand: true,
cwd: '.tmp/concat/scripts',
src: '*.js',
dest: '.tmp/concat/scripts'
}]
}
},
// Copies remaining files to places other tasks can use
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: [
'favicon.ico',
'manifest.json',
'robots.txt',
'apple-app-site-association',
'sitemap.xml',
'*.html',
'views/**/*.html',
'components/**/*.{html,svg}',
'images/**/*.{webp,svg}', // not minified
'styles/fonts/**/*.{eot,ttf,woff}'
]
}, {
expand: true,
src: 'support/service-worker/*.js',
dest: '<%= yeoman.dist %>'
}]
},
// Copy the README.md file to /docs folder and rename it to index.ngdoc
// so that it's available as the home page for our documentation website
ngdocIndex: {
files: [{
expand: true,
cwd: '.',
src: ['README.md'],
dest: 'docs/',
rename: function(dest) {
return dest + 'index.ngdoc';
}
}]
}
},
// Add @ngdoc directives to index.ngdoc so it can be imported properly
file_append: {
default_options: {
files: [function() {
return {
prepend: '@ngdoc overview\n@name API Reference\n@description\n\n',
input: 'docs/index.ngdoc'
};
}]
}
},
ngdocs: {
options: {
html5Mode: false,
title: 'mobile-web',
},
api: {
src: ['app/scripts/**/*.js', 'app/components/**/*.js',
'docs/index.ngdoc'],
title: 'Mobile Site Documentation'
}
},
// Run some tasks in parallel to speed up the build process
concurrent: {
server: [
'less:server'
],
test: [
'less:dist'
],
dist: [
'less:dist',
'imagemin'
]
},
// Compiles Less files to CSS
less: {
server: {
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/base.less'
}
},
dist: {
options: {
cleancss: true
},
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/base.less'
}
},
patternLibrary: {
files: {
'pattern-library/public/styles/main.css':
'<%= yeoman.app %>/styles/base.less'
}
}
},
// Generates a 'configs' angular module with configuration constants
ngconstant: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js',
name: 'configs',
wrap: '"use strict";\n\n {%= __ngModule %}'
},
dist: {
constants: function() {
return './config/' + grunt.config('config') + '.json';
}
},
test: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js',
},
constants: './config/production.json'
}
},
// Inject configs into index.html
replace: {
dist: {
options: {
patterns: [{
match: 'SENTRY_DSN_URL',
replacement: function() {
return grunt.file.readJSON('./config/' +
grunt.config('config') + '.json').SENTRY_DSN_URL;
},
expression: false
},
{
match: 'VERSION',
replacement: '<%= gitinfo.local.branch.current.shortSHA.toUpperCase() %>', // jshint ignore:line
expression: false
}]
},
files: {
'<%= yeoman.dist %>/index.html': '<%= yeoman.dist %>/index.html'
}
}
},
// Karma Test Runner. Run the Unit and Intergration (e2e) tests.
karma: {
options: {
configFile: 'karma.conf.js',
files: require('main-bower-files')({
includeSelf: true,
includeDev: true
}).map(function(file) {
return require('path').relative(__dirname, file);
}).concat([
'app/scripts/config.js',
'app/bower_components/' +
'angular-google-places-autocomplete/dist/autocomplete.min.js',
'app/bower_components/**/*.mock.js',
'test/mocks.js',
'app/scripts/**/*.spec.js',
'app/scripts/**/*.mock.js',
'app/scripts/services/**/*.spec.js',
// temporary until tests under components/ are fixed
'app/components/hireme-modal/*.spec.js',
'app/components/**/*.html',
'app/views/*.html'
])
},
dev: {
autoWatch: true,
singleRun: false,
reporters: ['mocha']
},
release: {
autoWatch: false,
singleRun: true,
reporters: ['mocha', 'coverage']
},
debug: {
autoWatch: true,
singleRun: false,
browsers: ['Chrome'],
preprocessors: [],
reporters: ['mocha']
},
nyan: {
autoWatch: false,
singleRun: true,
preprocessors: [],
reporters: ['nyan']
}
},
// E2e tests runner
protractor: {
options: {
keepAlive: true,
},
local: {
options: {
configFile: 'test/protractor.conf.js'
}
},
sauce: {
options: {
configFile: 'test/sauce.conf.js'
}
}
},
// Use that task to execute miscellaneous command lines
exec: {
rsyncStaging: {
cmd: 'rsync -e "ssh -l andrea.gherardi" www/ ' +
'mdevwww.freelancer.com:/var/www/mobile/ -rvz'
},
bowerInstall: {
cmd: './node_modules/.bin/bower --allow-root install'
},
createSymlinks: {
cmd: './script/create-symlinks.sh ./fl-angular/src/'
},
removeSymlinks: {
cmd: './script/remove-symlinks.sh ./fl-angular/src/'
}
},
// Inline the common templates (menu, primary nav, ...) into the index.html
// to limit round trip on app init
ng_template: {
options: {
appDir: '<%= yeoman.dist %>',
indexFile: 'index.html',
concat: true
},
files: [
'<%= yeoman.dist %>/views/menu.html',
'<%= yeoman.dist %>/views/main.html',
'<%= yeoman.dist %>/components/mobile-footer/mobile-footer.html',
'<%= yeoman.dist %>/components/posting-guide/posting-guide.html'
]
},
// keep up to date the bower & npm dependencies
checkDependencies: {
npm: {
options: {
packageManager: 'npm',
install: true
}
}
},
threepioExtract: {
pot: {
options: {
base: '<%= yeoman.app %>'
},
files: {
'localization/mobile-web.pot': [
'<%= yeoman.app %>/**/*.html',
'!<%= yeoman.app %>/bower_components/**/*',
'!<%= yeoman.app %>/views/bid-proposals.html'
]
}
}
},
threepioCompile: {
json: {
files: [{
expand: true,
cwd: 'localization',
src: '*.po',
dest: '<%= yeoman.app %>/translations/',
ext: '.json'
}]
},
js: {
options: {
wrap: 'angular.module("threepio").run(function(TranslationCache) {' +
' TranslationCache.put("${lang}", ${dict});' +
'});'
},
files: [{
expand: true,
cwd: 'localization',
src: '*.po',
dest: '<%= yeoman.app %>/translations/',
ext: '.js'
}]
}
},
threepioTranslate: {
all: {
options: {
catalogs: 'localization/*.po',
html: {
missing: true
}
},
files: [{
expand: true,
cwd: '<%= yeoman.app %>',
src: '**/*.html',
ext: '.${lang}.html',
dest: '<%= yeoman.dist %>'
}]
}
},
coverage: {
default: {
options: {
thresholds: {
'statements': -351,
'branches': -270,
'lines': -350,
'functions': -90
},
dir: 'coverage',
root: 'test'
}
}
},
swPrecache: {
build: {
handleFetch: true,
rootDir: '<%= yeoman.dist %>',
importScripts: [
'support/service-worker/idb-helper.js',
'support/service-worker/offline-tracking.js'
]
},
dev: {
handleFetch: false,
rootDir: '<%= yeoman.app %>'
}
},
kss: {
options: {
source: 'pattern-library/components',
config: 'kss-config.json'
},
dist: {
files: {
'pattern-library/dest/': [ 'app/styles/' ]
}
}
}
});
// Run the app in a local environment, i.e. spawn a web server
// and use grunt-livereload to automatically refresh the browser
grunt.registerTask('serve', function(target, config) {
if (target === 'dist') {
if (config) {
grunt.warn('In dist mode serve does not accept a config: you need ' +
'to run "build:<config>" first"');
} else {
grunt.task.run([
'proxy',
'connect:dist:keepalive'
]);
}
} else {
if (target && !config) {
config = target;
} else {
// Default to staging config
config = 'staging';
}
grunt.log.ok('Hello there! You\'re using the "' + config +
'" config in dev mode');
grunt.config('config', config);
grunt.task.run([
'clean:server',
'ngconstant:dist',
'checkDependencies:npm',
'exec:bowerInstall',
'wiredep',
'concurrent:server',
'autoprefixer',
'proxy',
'connect:livereload',
'watch'
]);
}
});
// Build the docs
grunt.registerTask('build-docs', [
'copy:ngdocIndex',
'file_append',
'ngdocs'
]);
// Serve the docs
grunt.registerTask('docs', [
'build-docs',
'connect:ngdocs',
'watch:ngdocs'
]);
// Update Pattern Library Docs
grunt.registerTask('build-kss', [
'less:patternLibrary',
'kss:dist'
]);
// Serve the lib docs
grunt.registerTask('styleguide', [
'build-kss',
'connect:patternLibrary',
'watch:patternLibrary'
]);
grunt.registerTask('test', [
'karma:release',
'coverage'
]);
// Default task
grunt.registerTask('default', [
'checkDependencies',
'exec:bowerInstall',
'jshint',
'ngconstant:test',
'build-docs',
'karma:release',
'coverage'
]);
// Run by the CI server
grunt.registerTask('ci', [
'default'
]);
grunt.registerMultiTask('swPrecache', function() {
var done = this.async();
var rootDir = this.data.rootDir;
var handleFetch = this.data.handleFetch;
var importScripts = this.data.importScripts;
// TODO: Pass options instead of hardcoding here
var config = {
cacheId: 'gaf-mobile',
handleFetch: handleFetch,
importScripts: importScripts,
navigateFallback: '/',
staticFileGlobs: [
rootDir + '/styles/**.css',
rootDir + '/scripts/**.js',
rootDir + '/components/**/*.html',
rootDir + '/views/**/*.html',
rootDir + '/index.html',
// offline page
rootDir + '/images/global/*.fl-logo-color-v2.svg',
rootDir + '/images/404/*.universe.jpg',
rootDir + '/images/404/*.flying-unicorn-sheep.svg'
],
stripPrefix: rootDir,
verbose: true
};
swPrecache.write(path.join(rootDir, 'service-worker.js'), config,
function(error) {
if (error) {
grunt.fail.warn(error);
}
done();
});
});
// Define browsertest & unittest
// Add JSHint to the build task!!
grunt.registerTask('build', 'Build the app', function(config) {
if (!config && !grunt.option('config')) {
grunt.warn('A valid config must be specified: local,' +
' staging, or production (e.g., build:local)');
}
grunt.config('config', config);
grunt.task.run([
'clean:dist',
'gitinfo', // populate 'gitinfo' config object
'ngconstant:dist',
'checkDependencies:npm',
'exec:bowerInstall',
'wiredep',
'useminPrepare',
'concurrent:dist',
'autoprefixer',
'concat:generated', // usemin flow
'ngAnnotate',
'copy:dist',
'uglify:generated', // usemin flow
'cssmin:generated', // usemin flow
'rev',
'usemin',
'revLocalizedAssets',
'htmlmin', // minify the HTML files & partials
'replace', // inject configs into index.html
'ng_template', // inject common templates (nav, menu) into index.html
'prerenderConfig', // fl-prerender config,
'threepioExtract', // translations
'swPrecache:build' // generate service worker for precaching
]);
});
grunt.registerTask('revLocalizedAssets',
'Fix the revision of localized assets', function() {
// TODO
// For each image (recursive)
// For each localized image (next to the image)
// Copy the rev of the default language and assign it to the
// localized one
});
grunt.registerTask('dev', 'Turn on & off dev mode (fl angular libs as ' +
'symlinks)', function(on) {
if (on === 'on') {
grunt.task.run([
'exec:createSymlinks'
]);
} else if (on === 'off') {
grunt.task.run([
'exec:removeSymlinks',
'exec:bowerInstall'
]);
} else {
grunt.warn('Do you want to turn "on" (grunt dev:on) or "off" (grunt ' +
'dev:off) the dev mode?');
}
});
// Load custom tasks
grunt.loadTasks('./tasks');
};
;