├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── app ├── index.js └── templates │ ├── _Gruntfile.js │ ├── _bower.json │ ├── _gitignore │ ├── _layout.jade │ ├── _package.json │ ├── editorconfig │ ├── js │ └── app.js │ ├── jshintrc │ └── pages │ └── index.jade ├── package.json ├── sass └── index.js └── test ├── test-app.js ├── test-load.js └── test-sass.js /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.jade] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "expr": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "undef": true, 13 | "unused": true, 14 | "strict": true, 15 | "trailing": true, 16 | "indent": 4, 17 | "quotmark": true, 18 | "boss": true, 19 | "eqnull": true, 20 | "lastsemic": true, 21 | "browser": true, 22 | "node": true 23 | } 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-frontend [![Build Status](https://secure.travis-ci.org/nDmitry/generator-frontend.png?branch=master)](https://travis-ci.org/nDmitry/generator-frontend) ![David](https://david-dm.org/nDmitry/generator-frontend.png) 2 | 3 | Scaffolds out a boilerplate for front-end development with Grunt and Sass. 4 | 5 | * Local Connect web-server 6 | * Live reloading 7 | * Jade templates 8 | * Sass with Compass and Susy 9 | * Prefixing your CSS with Autoprefixer 10 | * CSS linting 11 | * CSS and JS concatenation/minification 12 | * Resources revving 13 | * Image optimization 14 | * And more... 15 | 16 | ## Getting started 17 | – Install [GraphicsMagick](http://www.graphicsmagick.org/README.html) (`brew install graphicsmagick` for OS X) 18 | - Make sure you have [yo](https://github.com/yeoman/yo) installed: `npm install -g yo` 19 | - Install the generator: `npm install [-g] generator-frontend` 20 | - Run: `yo frontend` 21 | 22 | ## Subgenerators 23 | The generator includes two subgenerators: `app` and `sass`. You can run them with these commands: `yo frontend:app` and `yo frontend:sass`. 24 | 25 | ### App subgenerator 26 | Scaffolds out some starting files and configs. 27 | 28 | ### Sass subgenerator 29 | Fetches my Sass [library](https://github.com/nDmitry/sass) and copies it to `sass` directory (by default). 30 | 31 | You should install some required gems first: 32 | 33 | ``` 34 | $ gem install sass compass susy 35 | ``` 36 | 37 | ## License 38 | [MIT License](http://en.wikipedia.org/wiki/MIT_License) 39 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | yo = require('yeoman-generator'); 5 | 6 | 7 | module.exports = yo.generators.Base.extend({ 8 | constructor: function(arg, options) { 9 | yo.generators.Base.apply(this, arguments); 10 | 11 | this.on('end', function() { 12 | this.installDependencies({ 13 | skipInstall: options['skip-install'] 14 | }); 15 | }); 16 | 17 | this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json'))); 18 | }, 19 | 20 | askFor: function() { 21 | var cb = this.async(); 22 | 23 | var prompts = [ 24 | { 25 | name: 'projectName', 26 | message: 'Project Name', 27 | default: path.basename(process.cwd()) 28 | }, 29 | { 30 | name: 'lang', 31 | message: 'Project Language', 32 | default: 'ru' 33 | } 34 | ]; 35 | 36 | this.prompt(prompts, function(props) { 37 | for (var prop in props) { 38 | if (props.hasOwnProperty(prop)) { 39 | this[prop] = props[prop]; 40 | } 41 | } 42 | 43 | cb(); 44 | }.bind(this)); 45 | 46 | }, 47 | 48 | scaffold: function() { 49 | this.directory('js/', 'js/'); 50 | this.directory('pages/', 'pages/'); 51 | 52 | this.mkdir('fonts'); 53 | 54 | this.copy('_gitignore', '.gitignore'); 55 | this.copy('editorconfig', '.editorconfig'); 56 | this.copy('jshintrc', '.jshintrc'); 57 | 58 | this.template('_bower.json', 'bower.json'); 59 | this.template('_package.json', 'package.json'); 60 | this.template('_Gruntfile.js', 'Gruntfile.js'); 61 | this.template('_layout.jade', 'pages/layout.jade'); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /app/templates/_Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %> 2 | 3 | module.exports = function(grunt) { 4 | 5 | 'use strict'; 6 | 7 | require('load-grunt-tasks')(grunt); 8 | require('time-grunt')(grunt); 9 | 10 | grunt.initConfig({ 11 | 12 | banner: '/*! <%%= grunt.template.today("yyyy-mm-dd, h:MM:ss TT") %> */\n', 13 | 14 | componentsDir: 'bower_components', 15 | buildDir: 'dist', 16 | cssDir: 'css', 17 | sassDir: 'sass', 18 | jsDir: 'js', 19 | imgDir: 'img', 20 | fontsDir: 'fonts', 21 | pagesDir: 'pages', 22 | 23 | connect: { 24 | server: { 25 | options: { 26 | hostname: '*', 27 | port: grunt.option('port') || 9001, 28 | base: ['<%%= buildDir %>/', './'], 29 | livereload: true 30 | } 31 | } 32 | }, 33 | 34 | clean: { 35 | build: {src: '<%%= buildDir %>/'}, 36 | css: {src: '<%%= buildDir %>/<%%= cssDir %>/main.css'}, 37 | tmp: {src: '.tmp/'} 38 | }, 39 | 40 | copy: { 41 | img: { 42 | src: '<%%= imgDir %>/**', 43 | dest: '<%%= buildDir %>/' 44 | }, 45 | 46 | fonts: { 47 | src: '<%%= fontsDir %>/{,*/}*.{eot,otf,svg,ttf,woff}', 48 | dest: '<%%= buildDir %>/' 49 | }, 50 | 51 | js: { 52 | src: '<%%= jsDir %>/**', 53 | dest: '<%%= buildDir %>/' 54 | } 55 | }, 56 | 57 | jade: { 58 | options: { 59 | pretty: true 60 | }, 61 | dist: { 62 | expand: true, 63 | flatten: true, 64 | ext: '.html', 65 | cwd: '<%%= pagesDir %>/', 66 | src: ['{,*/}*.jade', '!layout.jade'], 67 | dest: '<%%= buildDir %>/' 68 | } 69 | }, 70 | 71 | sass: { 72 | dist: { 73 | options: { 74 | require: 'susy', 75 | style: 'expanded', 76 | update: true, 77 | compass: true, 78 | sourcemap: 'none' 79 | }, 80 | src: '<%%= sassDir %>/main.scss', 81 | dest: '<%%= buildDir %>/<%%= cssDir %>/main.css' 82 | } 83 | }, 84 | 85 | autoprefixer: { 86 | options: { 87 | browsers: ['last 2 versions', 'Firefox ESR', 'Opera 12.1', 'Explorer >= 8'] 88 | }, 89 | dist: { 90 | src: '<%%= buildDir %>/<%%= cssDir %>/main.css' 91 | } 92 | }, 93 | 94 | csslint: { 95 | options: { 96 | 'adjoining-classes': false, 97 | 'box-model': false, 98 | 'box-sizing': false, 99 | 'compatible-vendor-prefixes': false, 100 | 'font-sizes': false, 101 | 'gradients': false, 102 | 'important': false, 103 | 'outline-none': false, 104 | 'regex-selectors': false, 105 | 'universal-selector': false, 106 | 'unqualified-attributes': false, 107 | 'bulletproof-font-face': false, 108 | 'unique-headings': false 109 | }, 110 | dist: { 111 | src: '<%%= buildDir %>/<%%= cssDir %>/main.css' 112 | } 113 | }, 114 | 115 | cssmin: { 116 | options: { 117 | banner: '<%%= banner %>', 118 | report: 'min' 119 | } 120 | }, 121 | 122 | uglify: { 123 | options: { 124 | report: 'min', 125 | banner: '<%%= banner %>' 126 | } 127 | }, 128 | 129 | useminPrepare: { 130 | options: { 131 | root: ['./', '<%%= buildDir %>/'] 132 | }, 133 | html: '<%%= buildDir %>/index.html' 134 | }, 135 | 136 | usemin: { 137 | html: '<%%= buildDir %>/*.html', 138 | }, 139 | 140 | filerev: { 141 | options: { 142 | length: 4 143 | }, 144 | dist: { 145 | src: [ 146 | '<%%= buildDir %>/<%%= cssDir %>/main.css', 147 | '<%%= buildDir %>/<%%= jsDir %>/app.js' 148 | ] 149 | } 150 | }, 151 | 152 | imagemin: { 153 | options: { 154 | use: [require('imagemin-pngquant')()] 155 | }, 156 | dist: { 157 | expand: true, 158 | cwd: '<%%= buildDir %>/<%%= imgDir %>/', 159 | src: ['{,*/}*.{png,jpg,jpeg,gif}'], 160 | dest: '<%%= buildDir %>/<%%= imgDir %>/' 161 | } 162 | }, 163 | 164 | compress: { 165 | dist: { 166 | options: { 167 | archive: '<%%= buildDir %>.zip' 168 | }, 169 | expand: true, 170 | cwd: '<%%= buildDir %>/', 171 | src: ['**', '../<%%= componentsDir %>/**', '../bower.json'], 172 | dest: './' 173 | }, 174 | min: { 175 | options: { 176 | archive: '<%%= buildDir %>.zip' 177 | }, 178 | expand: true, 179 | cwd: '<%%= buildDir %>/', 180 | src: '**', 181 | dest: './' 182 | } 183 | }, 184 | 185 | photobox: { 186 | task: { 187 | options: { 188 | indexPath: 'regression/', 189 | template: 'canvas', 190 | screenSizes: ['1280'], 191 | urls: [ 192 | 'http://localhost:9001', 193 | ] 194 | } 195 | } 196 | }, 197 | 198 | watch: { 199 | jade: { 200 | files: ['<%%= pagesDir %>/{,*/}*.jade'], 201 | tasks: ['jade'] 202 | }, 203 | 204 | sass: { 205 | files: ['<%%= sassDir %>/**/*.scss'], 206 | tasks: ['sass', 'autoprefixer'] 207 | }, 208 | 209 | livereload: { 210 | options: { 211 | livereload: true, 212 | }, 213 | files: [ 214 | '<%%= buildDir %>/**', 215 | '<%%= copy.img.src %>', 216 | '<%%= copy.fonts.src %>', 217 | '<%%= copy.js.src %>' 218 | ] 219 | } 220 | } 221 | 222 | }); 223 | 224 | // Compile all files that should be compiled 225 | grunt.registerTask('build', [ 226 | 'clean:build', 227 | 'jade', 228 | 'sass', 229 | 'autoprefixer' 230 | ]); 231 | 232 | // Create non-minified project snapshot in build directory and compress it to zip 233 | grunt.registerTask('dist', [ 234 | 'build', 235 | 'copy', 236 | 'imagemin', 237 | 'photobox', 238 | 'compress:dist' 239 | ]); 240 | 241 | grunt.registerTask('serve', [ 242 | 'connect', 243 | 'watch' 244 | ]); 245 | 246 | grunt.registerTask('lint', [ 247 | 'csslint' 248 | ]); 249 | 250 | // Minify all JS and CSS, optimize images, rev JS and CSS and replace paths in HTML 251 | grunt.registerTask('minify', [ 252 | 'build', 253 | 'copy:img', 'copy:fonts', 254 | 'useminPrepare', 255 | 'concat', 256 | 'cssmin', 257 | 'uglify', 258 | 'filerev', 259 | 'usemin', 260 | 'imagemin', 261 | 'clean:css', 262 | 'compress:min', 263 | 'clean:tmp' 264 | ]); 265 | 266 | grunt.registerTask('default', [ 267 | 'build' 268 | ]); 269 | }; 270 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(projectName) %>", 3 | "private": true, 4 | "dependencies": { 5 | "normalize.css": "~3.0.1", 6 | "es5-shim": "~4.0.3", 7 | "es6-shim": "~0.20.0", 8 | "jquery": "~1.11.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/templates/_gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .idea/ 3 | *.sublime-* 4 | node_modules/ 5 | bower_components/ 6 | regression/ 7 | dist/ 8 | dist.zip 9 | -------------------------------------------------------------------------------- /app/templates/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | 4 | 5 | 6 | head 7 | meta(charset='utf-8') 8 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 9 | 10 | block title 11 | title <%= _.capitalize(projectName) %> 12 | 13 | meta(name='viewport' content='width=device-width, initial-scale=1') 14 | 15 | // build:css css/main.css 16 | link(rel='stylesheet', href='bower_components/normalize.css/normalize.css') 17 | link(rel='stylesheet', href='css/main.css') 18 | // endbuild 19 | 20 | 23 | 24 | body 25 | .l-wrapper 26 | div(class='l-header', role='banner') 27 | nav(class='nav', role='navigation') 28 | 29 | main(role='main') 30 | block content 31 | 32 | div(class='l-footer', role='contentinfo') 33 | 34 | // build:js js/app.js 35 | script(src='bower_components/es5-shim/es5-shim.js') 36 | script(src='bower_components/es6-shim/es6-shim.js') 37 | script(src='bower_components/jquery/dist/jquery.js') 38 | script(src='js/app.js') 39 | // endbuild 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(projectName) %>", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "grunt && grunt serve" 7 | }, 8 | "devDependencies": { 9 | "grunt": "^0.4.4", 10 | "grunt-autoprefixer": "^1.0.1", 11 | "grunt-contrib-clean": "^0.6.0", 12 | "grunt-contrib-compress": "^0.12.0", 13 | "grunt-contrib-concat": "^0.5.0", 14 | "grunt-contrib-connect": "^0.8.0", 15 | "grunt-contrib-copy": "^0.7.0", 16 | "grunt-contrib-cssmin": "^0.10.0", 17 | "grunt-contrib-csslint": "^0.3.1", 18 | "grunt-contrib-imagemin": "^0.9.1", 19 | "grunt-contrib-jade": "^0.13.0", 20 | "grunt-contrib-sass": "^0.8.1", 21 | "grunt-contrib-uglify": "^0.6.0", 22 | "grunt-contrib-watch": "^0.6.1", 23 | "grunt-filerev": "^2.0.0", 24 | "grunt-photobox": "^0.8.0", 25 | "grunt-usemin": "^2.3.0", 26 | "imagemin-pngquant": "^4.0.0", 27 | "load-grunt-tasks": "^1.0.0", 28 | "time-grunt": "^1.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | ; http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [{*.scss,*.css,*.jade,*.html}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /app/templates/js/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 'use strict'; 4 | 5 | 6 | }()); 7 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "expr": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "nonew": true, 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "indent": 4, 19 | "quotmark": true, 20 | "boss": true, 21 | "eqnull": true, 22 | "lastsemic": true, 23 | "browser": true, 24 | "jquery": true, 25 | "node": true, 26 | "worker": true 27 | } 28 | -------------------------------------------------------------------------------- /app/templates/pages/index.jade: -------------------------------------------------------------------------------- 1 | extends ./layout 2 | 3 | block content 4 | //- delete me 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-frontend", 3 | "version": "5.0.0", 4 | "description": "Scaffolds out a boilerplate for front-end development.", 5 | "keywords": [ 6 | "yeoman-generator" 7 | ], 8 | "files": [ 9 | "app", 10 | "sass" 11 | ], 12 | "author": { 13 | "name": "Dmitry Nikitenko", 14 | "email": "dima.nikitenko@gmail.com", 15 | "url": "https://github.com/nDmitry" 16 | }, 17 | "main": "app/index.js", 18 | "repository": "nDmitry/generator-frontend", 19 | "scripts": { 20 | "test": "mocha" 21 | }, 22 | "dependencies": { 23 | "yeoman-generator": "^0.17.1" 24 | }, 25 | "devDependencies": { 26 | "mocha": "^2.0.1" 27 | }, 28 | "engines": { 29 | "node": ">=0.10.0" 30 | }, 31 | "licenses": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /sass/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var yo = require('yeoman-generator'); 4 | 5 | module.exports = yo.generators.Base.extend({ 6 | askFor: function() { 7 | var cb = this.async(); 8 | 9 | var prompts = [ 10 | { 11 | name: 'path', 12 | message: 'Path', 13 | default: 'sass/' 14 | } 15 | ]; 16 | 17 | this.prompt(prompts, function(props) { 18 | for (var prop in props) { 19 | if (props.hasOwnProperty(prop)) { 20 | this[prop] = props[prop]; 21 | } 22 | } 23 | 24 | cb(); 25 | }.bind(this)); 26 | }, 27 | 28 | scaffold: function() { 29 | var cb = this.async(); 30 | 31 | this.log.info('Fetching Sass framework...'); 32 | 33 | this.remote('nDmitry', 'sass', function(err, remote) { 34 | if (err) { 35 | return cb(err); 36 | } 37 | 38 | remote.directory('sass/', this.path); 39 | remote.copy('config.rb', 'config.rb'); 40 | 41 | cb(); 42 | }.bind(this)); 43 | 44 | this.mkdir('img/sprites'); 45 | this.mkdir('img/sprites_2x'); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | 3 | 'use strict'; 4 | 5 | var path = require('path'); 6 | var helpers = require('yeoman-generator').test; 7 | 8 | 9 | describe('app generator', function() { 10 | this.timeout(10000); 11 | 12 | beforeEach(function(done) { 13 | helpers.testDirectory(path.join(__dirname, 'temp'), function(err) { 14 | if (err) { 15 | return done(err); 16 | } 17 | 18 | this.app = helpers.createGenerator('frontend:app', [ 19 | '../../app' 20 | ]); 21 | 22 | this.app.options['skip-install'] = true; 23 | 24 | done(); 25 | }.bind(this)); 26 | }); 27 | 28 | it('creates expected files', function(done) { 29 | var expected = [ 30 | 'pages/layout.jade', 31 | 'pages/index.jade', 32 | 'js/app.js', 33 | '.gitignore', 34 | '.editorconfig', 35 | '.jshintrc', 36 | 'Gruntfile.js', 37 | 'bower.json', 38 | 'package.json' 39 | ]; 40 | 41 | helpers.mockPrompt(this.app, { 42 | projectName: 'temp', 43 | lang: 'ru' 44 | }); 45 | 46 | this.app.run({}, function() { 47 | helpers.assertFile(expected); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('replaces templates variables', function(done) { 53 | var expected = [ 54 | ['pages/layout.jade', /title Temp/, /lang="ru"/], 55 | ['bower.json', /"name": "temp"/], 56 | ['package.json', /"name": "temp"/], 57 | ]; 58 | 59 | helpers.mockPrompt(this.app, { 60 | projectName: 'temp', 61 | lang: 'ru' 62 | }); 63 | 64 | this.app.run({}, function() { 65 | helpers.assertFileContent(expected); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/test-load.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert'); 6 | 7 | describe('Generator', function() { 8 | it('can be imported without blowing up (app)', function() { 9 | var app = require('../app'); 10 | assert(app !== undefined); 11 | }); 12 | 13 | it('can be imported without blowing up (sass)', function() { 14 | var stylus = require('../sass'); 15 | assert(stylus !== undefined); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/test-sass.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | 3 | 'use strict'; 4 | 5 | var path = require('path'); 6 | var helpers = require('yeoman-generator').test; 7 | 8 | 9 | describe('sass generator', function() { 10 | this.timeout(10000); 11 | 12 | beforeEach(function(done) { 13 | helpers.testDirectory(path.join(__dirname, 'temp'), function(err) { 14 | if (err) { 15 | return done(err); 16 | } 17 | 18 | this.app = helpers.createGenerator('frontend:sass', [ 19 | '../../sass' 20 | ]); 21 | 22 | this.app.options['skip-install'] = true; 23 | 24 | done(); 25 | }.bind(this)); 26 | }); 27 | 28 | it('creates expected files', function(done) { 29 | var expected = [ 30 | 'sass/main.scss', 31 | 'config.rb' 32 | ]; 33 | 34 | helpers.mockPrompt(this.app, { 35 | path: 'sass/' 36 | }); 37 | 38 | this.app.run({}, function() { 39 | helpers.assertFile(expected); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | --------------------------------------------------------------------------------