diff --git a/.gitignore b/.gitignore index db9a64ac62..c0f0054b81 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ config.json npm-debug.log lib public/bower_components -public/build +build src/*/*.map src/*/*/*.map diff --git a/.nodemonignore b/.nodemonignore index e384b68122..17de44ee85 100644 --- a/.nodemonignore +++ b/.nodemonignore @@ -1,3 +1,4 @@ public/* views/* +build/* Gruntfile.js \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 6d0b544422..54dc3ab9ca 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -5,13 +5,13 @@ module.exports = function(grunt) { grunt.initConfig({ clean: { - build: ['public/build'] + build: ['build'] }, uglify: { buildApp: { files: { - 'public/build/app.js': [ + 'build/app.js': [ 'public/bower_components/jquery/jquery.min.js', 'public/bower_components/bootstrap-growl/jquery.bootstrap-growl.min.js', 'public/bower_components/angular/angular.min.js', @@ -65,7 +65,7 @@ module.exports = function(grunt) { }, buildStatic: { files: { - 'public/build/static.js': [ + 'build/static.js': [ 'public/bower_components/jquery/jquery.min.js', 'public/bower_components/habitrpg-shared/dist/habitrpg-shared.js', 'public/bower_components/angular/angular.min.js', @@ -89,8 +89,8 @@ module.exports = function(grunt) { paths: ['public'] }, files: { - 'public/build/app.css': ['public/css/index.styl'], - 'public/build/static.css': ['public/css/static.styl'] + 'build/app.css': ['public/css/index.styl'], + 'build/static.css': ['public/css/static.styl'] } } }, @@ -98,15 +98,40 @@ module.exports = function(grunt) { cssmin: { build: { files: { - 'public/build/app.css': ['public/build/app.css'], - 'public/build/static.css': ['public/build/static.css'] + 'build/app.css': ['build/app.css'], + 'build/static.css': ['build/static.css'], + 'build/bower_components/habitrpg-shared/dist/spritesheets.css': ['public/bower_components/habitrpg-shared/dist/spritesheets.css'], + 'build/bower_components/bootstrap/docs/assets/css/bootstrap.css': ['public/bower_components/bootstrap/docs/assets/css/bootstrap.css'], + 'build/bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css': ['public/bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css'], + 'build/bower_components/bootstrap/docs/assets/css/docs.css': ['public/bower_components/bootstrap/docs/assets/css/docs.css'] } } }, + copy: { + build: { + files: [{expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'}] + } + }, + + // UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED! + hashres: { + build: { + options: { + fileNameFormat: '${name}-${hash}.${ext}' + }, + src: [ + 'build/*.js', 'build/*.css', 'build/favicon.ico', + 'build/bower_components/bootstrap/docs/assets/css/*.css', + 'build/bower_components/habitrpg-shared/dist/*.css' + ], + dest: 'make-sure-i-do-not-exist' + } + }, + nodemon: { dev: { - ignoredFiles: ['public/*', 'Gruntfile.js', 'views/*'] // Do not work! + ignoredFiles: ['public/*', 'Gruntfile.js', 'views/*', 'build/*'] } }, @@ -130,8 +155,8 @@ module.exports = function(grunt) { }); // Register tasks. - grunt.registerTask('build:prod', ['clean:build', 'uglify', 'stylus', 'cssmin']); - grunt.registerTask('build:dev', ['clean:build', 'stylus', 'cssmin']); + grunt.registerTask('build:prod', ['clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']); + grunt.registerTask('build:dev', ['clean:build', 'stylus', 'cssmin', 'copy:build', 'hashres']); grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]); @@ -140,8 +165,10 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-stylus'); grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-nodemon'); grunt.loadNpmTasks('grunt-concurrent'); grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-hashres'); }; diff --git a/package.json b/package.json index 5e24f91961..ce0234c1a9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "grunt-contrib-stylus": "~0.8.0", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-cssmin": "~0.6.1", - "grunt-contrib-watch": "~0.5.x", + "grunt-contrib-watch": "~0.5.3", + "grunt-contrib-copy": "~0.4.1", + "grunt-hashres": "~0.3.2", "grunt-nodemon": "~0.1.1", "grunt-concurrent": "~0.3.1", "bower": "~1.2.4", diff --git a/src/middleware.js b/src/middleware.js index 376b00f177..67afee8461 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,5 +1,7 @@ var nconf = require('nconf'); var _ = require('lodash'); +var fs = require('fs'); +var path = require('path'); module.exports.forceSSL = function(req, res, next){ var baseUrl = nconf.get("BASE_URL"); @@ -26,12 +28,45 @@ module.exports.cors = function(req, res, next) { return next(); }; +var buildFiles = []; + +var walk = function(folder){ + var res = fs.readdirSync(folder); + + res.forEach(function(fileName){ + file = folder + '/' + fileName; + if(fs.statSync(file).isDirectory()){ + walk(file); + }else{ + var relFolder = path.relative(path.join(__dirname, "/../build"), folder); + var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); + + if(relFolder){ + old = relFolder + '/' + old; + fileName = relFolder + '/' + fileName; + } + + buildFiles[old] = fileName + } + }); +} + +walk(path.join(__dirname, "/../build")); + +var getBuildUrl = function(url){ + console.log("CALLED ", url) + if(buildFiles[url]) return buildFiles[url]; + + return filename; +} + module.exports.locals = function(req, res, next) { res.locals.habitrpg = res.locals.habitrpg || {} _.defaults(res.locals.habitrpg, { NODE_ENV: nconf.get('NODE_ENV'), IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), - STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY') + STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), + getBuildUrl: getBuildUrl }); next() } diff --git a/src/server.js b/src/server.js index c38e877895..cb1a0c18f0 100644 --- a/src/server.js +++ b/src/server.js @@ -99,6 +99,9 @@ app.use(passport.initialize()); app.use(passport.session()); app.use(app.router); + +var oneYear = 31536000000; +app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: oneYear })); app.use(express['static'](path.join(__dirname, "/../public"))); // development only diff --git a/views/index.jade b/views/index.jade index 0e49804f75..33d3f8212a 100644 --- a/views/index.jade +++ b/views/index.jade @@ -4,7 +4,7 @@ html title HabitRPG | Your Life The Role Playing Game // ?v=1 needed to force refresh - link(rel='shortcut icon' href='/favicon.ico?v=1') + link(rel='shortcut icon' href='/#{env.getBuildUrl("favicon.ico")}?v=2') script(type='text/javascript'). window.env = !{JSON.stringify(env)}; @@ -14,16 +14,16 @@ html display: none; } - // CSS - link(rel='stylesheet', href='/bower_components/bootstrap/docs/assets/css/bootstrap.css') + // CSS Remember to update also in Grunfile.js cssmin task! + link(rel='stylesheet', href='/#{env.getBuildUrl("bower_components/bootstrap/docs/assets/css/bootstrap.css")}') - link(rel='stylesheet', href='/build/app.css') + link(rel='stylesheet', href='/#{env.getBuildUrl("app.css")}') // HabitRPG Shared - link(rel='stylesheet', href='/bower_components/habitrpg-shared/dist/spritesheets.css') + link(rel='stylesheet', href='/#{env.getBuildUrl("bower_components/habitrpg-shared/dist/spritesheets.css")}') - if(env.NODE_ENV == 'production'){ - script(type='text/javascript', src='build/app.js') + script(type='text/javascript', src='/#{env.getBuildUrl("app.js")}') - }else{ // Remember to update the file list in Gruntfile.js! script(type='text/javascript', src='/bower_components/jquery/jquery.min.js') diff --git a/views/static/layout.jade b/views/static/layout.jade index f9b5ef3e8d..07814bd1dc 100644 --- a/views/static/layout.jade +++ b/views/static/layout.jade @@ -7,23 +7,22 @@ html block title title HabitRPG | Your Life the Role Playing Game - // ?v=1 needed to force refresh - link(rel='shortcut icon' href='/favicon.ico?v=1') + link(rel='shortcut icon', href='/#{env.getBuildUrl("favicon.ico")}?v=2') meta(charset='utf-8') meta(name='viewport', content='width=device-width, initial-scale=1.0') - // CSS - link(rel='stylesheet', href='/bower_components/bootstrap/docs/assets/css/bootstrap.css') + // CSS Remember to update also in Grunfile.js cssmin task! + link(rel='stylesheet', href='/#{env.getBuildUrl("bower_components/bootstrap/docs/assets/css/bootstrap.css")}') // Keep this out of build because the one after has some images and would like not to override it - link(rel='stylesheet', href='/bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css') - link(href='/bower_components/bootstrap/docs/assets/css/docs.css', rel='stylesheet') + link(rel='stylesheet', href='/#{env.getBuildUrl("bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css")}') + link(rel='stylesheet', href='/#{env.getBuildUrl("bower_components/bootstrap/docs/assets/css/docs.css")}') - link(rel='stylesheet', href='/build/static.css') + link(rel='stylesheet', href='/#{env.getBuildUrl("static.css")}') // JS - if(layoutEnv.NODE_ENV == 'production'){ - script(type='text/javascript', src='/build/static.js') + script(type='text/javascript', src='/#{env.getBuildUrl("static.js")') - }else{ // Remember to update the file list in Gruntfile.js! script(type='text/javascript', src='/bower_components/jquery/jquery.min.js')