├── app ├── templates │ ├── gitattributes │ ├── bowerrc │ ├── gitignore │ ├── robots.txt │ ├── main.js │ ├── favicon.ico │ ├── apple-touch-icon.png │ ├── jshintrc │ ├── editorconfig │ ├── _package.json │ ├── main.scss │ ├── main.css │ ├── index.html │ └── Gruntfile.js ├── USAGE └── index.js ├── yeoman.png ├── contributing.md ├── test ├── babel.js ├── eslint.js ├── sass.js ├── modernizr.js ├── general.js ├── jquery.js ├── test-framework.js └── bootstrap.js ├── package.json ├── docs └── recipes │ ├── README.md │ ├── compass.md │ └── assemble.md └── readme.md /app/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/templates/bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | bower_components 5 | -------------------------------------------------------------------------------- /app/templates/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bassjobsen/generator-bootstrap4/HEAD/yeoman.png -------------------------------------------------------------------------------- /app/templates/main.js: -------------------------------------------------------------------------------- 1 | console.log('\'Allo \'Allo!'); // eslint-disable-line no-console 2 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | See the [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md) 2 | -------------------------------------------------------------------------------- /app/templates/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bassjobsen/generator-bootstrap4/HEAD/app/templates/favicon.ico -------------------------------------------------------------------------------- /app/templates/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bassjobsen/generator-bootstrap4/HEAD/app/templates/apple-touch-icon.png -------------------------------------------------------------------------------- /app/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Creates a new basic front-end web application. 3 | 4 | Options: 5 | Bootstrap: Include Bootstrap for Sass 6 | 7 | Example: 8 | yo webapp 9 | 10 | This will create: 11 | Gruntfile.js: Configuration for the task runner. 12 | bower.json: Front-end packages installed by bower. 13 | package.json: Development packages installed by npm. 14 | 15 | app/: Your application files. 16 | test/: Unit tests for your application. 17 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "esnext": true, 8 | "immed": true,<% if (testFramework === 'jasmine') { %> 9 | "jasmine": true,<% } %> 10 | "jquery": true, 11 | "latedef": true,<% if (testFramework === 'mocha') { %> 12 | "mocha": true,<% } %> 13 | "newcap": true, 14 | "noarg": true, 15 | "quotmark": "single", 16 | "strict": true, 17 | "undef": true, 18 | "unused": true 19 | } 20 | -------------------------------------------------------------------------------- /app/templates/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 | -------------------------------------------------------------------------------- /test/babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('babel', function () { 7 | before(function (done) { 8 | helpers.run(path.join(__dirname, '../app')) 9 | .inDir(path.join(__dirname, '.tmp')) 10 | .withOptions({'skip-install': true, babel: true}) 11 | .withPrompts({features: []}) 12 | .on('end', done); 13 | }); 14 | 15 | it('adds the Grunt plugin', function () { 16 | assert.fileContent('package.json', 'babel'); 17 | }); 18 | 19 | it('adds the es6 eslint env', function () { 20 | assert.fileContent('package.json', '"es6": true'); 21 | }); 22 | 23 | it('adds the Grunt task', function () { 24 | assert.fileContent('Gruntfile.js', 'babel'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/eslint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('eslint', function () { 7 | before(function (done) { 8 | helpers.run(path.join(__dirname, '../app')) 9 | .inDir(path.join(__dirname, '.tmp')) 10 | .withOptions({'skip-install': true}) 11 | .withPrompts({features: []}) 12 | .on('end', done); 13 | }); 14 | 15 | it('adds Grunt plugin', function () { 16 | assert.fileContent('package.json', 'grunt-eslint'); 17 | }); 18 | 19 | it('adds basic configuration', function () { 20 | assert.fileContent('package.json', 'eslintConfig'); 21 | }); 22 | 23 | it('adds eslint recommended rule', function () { 24 | assert.fileContent('package.json', 'eslint:recommended'); 25 | }); 26 | 27 | it('adds Grunt task', function () { 28 | assert.fileContent('Gruntfile.js', 'eslint'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/sass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('sass', function () { 7 | before(function (done) { 8 | helpers.run(path.join(__dirname, '../app')) 9 | .inDir(path.join(__dirname, '.tmp')) 10 | .withOptions({'skip-install': true}) 11 | .withPrompts({features: [ 12 | 'includeSass' 13 | ]}) 14 | .on('end', done); 15 | }); 16 | 17 | it('uses SCSS', function () { 18 | assert.file('app/styles/main.scss'); 19 | assert.noFile('app/styles/main.css'); 20 | }); 21 | 22 | it('adds the Grunt plugin', function () { 23 | assert.fileContent('package.json', 'sass'); 24 | }); 25 | 26 | it('adds the Grunt task', function () { 27 | assert.fileContent('Gruntfile.js', 'sass'); 28 | }); 29 | 30 | it('adds the HTML description', function () { 31 | assert.fileContent('app/index.html', 'Sass'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/modernizr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('modernizr', function () { 7 | before(function (done) { 8 | helpers.run(path.join(__dirname, '../app')) 9 | .inDir(path.join(__dirname, '.tmp')) 10 | .withOptions({'skip-install': true}) 11 | .withPrompts({features: [ 12 | 'includeModernizr' 13 | ]}) 14 | .on('end', done); 15 | }); 16 | 17 | it('adds the Bower dependency', function () { 18 | assert.fileContent('bower.json', 'modernizr'); 19 | }); 20 | 21 | it('adds the Grunt plugin', function () { 22 | assert.fileContent('package.json', 'modernizr'); 23 | }); 24 | 25 | it('adds the Grunt task', function () { 26 | assert.fileContent('Gruntfile.js', 'modernizr'); 27 | }); 28 | 29 | it('adds the HTML description', function () { 30 | assert.fileContent('app/index.html', 'Modernizr'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/general.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('general', function () { 7 | before(function (done) { 8 | helpers.run(path.join(__dirname, '../app')) 9 | .inDir(path.join(__dirname, '.tmp')) 10 | .withOptions({'skip-install': true}) 11 | .withPrompts({features: []}) 12 | .on('end', done); 13 | }); 14 | 15 | // not testing the actual run of generators yet 16 | it('can be required without throwing', function () { 17 | this.app = require('../app'); 18 | }); 19 | 20 | it('creates expected files', function () { 21 | assert.file([ 22 | 'package.json', 23 | 'bower.json', 24 | '.bowerrc', 25 | 'Gruntfile.js', 26 | 'app/favicon.ico', 27 | 'app/apple-touch-icon.png', 28 | 'app/index.html', 29 | 'app/scripts/main.js', 30 | 'app/styles/main.css', 31 | 'app/robots.txt', 32 | 'test', 33 | '.editorconfig', 34 | '.gitignore', 35 | '.gitattributes' 36 | ]); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-bootstrap4", 3 | "version": "0.0.2", 4 | "description": "Scaffold out a front-end Bootstrap 4 Web app", 5 | "license": "BSD", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/bassjobsen/generator-bootstrap4.git" 9 | }, 10 | "author": "Bass Jobsen", 11 | "homepage": "https://github.com/bassjobsen/generator-bootstrap4/", 12 | "main": "app/index.js", 13 | "scripts": { 14 | "test": "mocha --reporter spec" 15 | }, 16 | "files": [ 17 | "app" 18 | ], 19 | "keywords": [ 20 | "yeoman-generator", 21 | "web", 22 | "app", 23 | "bootstrap", 24 | "bootstrap 4", 25 | "front-end", 26 | "jquery", 27 | "grunt" 28 | ], 29 | "dependencies": { 30 | "chalk": "^1.0.0", 31 | "generator-jasmine": "^0.2.0", 32 | "generator-mocha": "^0.2.0", 33 | "mkdirp": "^0.5.1", 34 | "underscore.string": "^3.1.1", 35 | "wiredep": "^2.2.2", 36 | "yeoman-generator": "^0.20.1", 37 | "yosay": "^1.0.4" 38 | }, 39 | "devDependencies": { 40 | "grunt-cli": "^0.1.13", 41 | "mocha": "*", 42 | "yeoman-assert": "^2.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/jquery.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('jquery', function () { 7 | describe('on', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../app')) 10 | .inDir(path.join(__dirname, '.tmp')) 11 | .withOptions({'skip-install': true}) 12 | .withPrompts({ 13 | features: [], 14 | includeJQuery: true 15 | }) 16 | .on('end', done); 17 | }); 18 | 19 | it('adds the bower dependency', function () { 20 | assert.fileContent('bower.json', '"jquery"'); 21 | }); 22 | 23 | it('uses the eslint environment', function () { 24 | assert.fileContent('package.json', '"jquery"'); 25 | }); 26 | }); 27 | 28 | describe('off', function () { 29 | before(function (done) { 30 | helpers.run(path.join(__dirname, '../app')) 31 | .inDir(path.join(__dirname, '.tmp')) 32 | .withOptions({'skip-install': true}) 33 | .withPrompts({ 34 | features: [], 35 | includeJQuery: false 36 | }) 37 | .on('end', done); 38 | }); 39 | 40 | it('doesn\'t add the bower dependency', function () { 41 | assert.noFileContent('bower.json', '"jquery"'); 42 | }); 43 | 44 | it('doesn\'t uses the ESLint environment', function () { 45 | assert.noFileContent('package.json', '"jquery"'); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/test-framework.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('test framework', function () { 7 | describe('mocha', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../app')) 10 | .inDir(path.join(__dirname, '.tmp')) 11 | .withOptions({ 12 | 'skip-install': true, 13 | 'test-framework': 'mocha' 14 | }) 15 | .withPrompts({features: []}) 16 | .on('end', done); 17 | }); 18 | 19 | it('adds the Grunt plugin', function () { 20 | assert.fileContent('package.json', '"grunt-mocha"'); 21 | }); 22 | 23 | it('adds the Grunt task', function () { 24 | assert.fileContent('Gruntfile.js', 'mocha'); 25 | }); 26 | 27 | it('uses the ESLint environment', function () { 28 | assert.fileContent('package.json', '"mocha"'); 29 | }); 30 | }); 31 | 32 | describe('jasmine', function () { 33 | before(function (done) { 34 | helpers.run(path.join(__dirname, '../app')) 35 | .inDir(path.join(__dirname, '.tmp')) 36 | .withOptions({ 37 | 'skip-install': true, 38 | 'test-framework': 'jasmine' 39 | }) 40 | .withPrompts({features: []}) 41 | .on('end', done); 42 | }); 43 | 44 | it('adds the Grunt plugin', function () { 45 | assert.fileContent('package.json', '"grunt-contrib-jasmine"'); 46 | }); 47 | 48 | it('adds the Grunt task', function () { 49 | assert.fileContent('Gruntfile.js', 'jasmine'); 50 | }); 51 | 52 | it('uses the ESLint environment', function () { 53 | assert.fileContent('package.json', '"jasmine"'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "autoprefixer": "^6.0.2", 5 | "grunt": "^0.4.5", 6 | <% if (useBabel) { -%> 7 | "grunt-babel": "^5.0.0", 8 | <% } -%> 9 | "grunt-browser-sync": "^2.1.2", 10 | "grunt-concurrent": "^1.0.0", 11 | "grunt-contrib-clean": "^0.6.0", 12 | "grunt-contrib-concat": "^0.5.1", 13 | "grunt-contrib-copy": "^0.8.0", 14 | "grunt-contrib-cssmin": "^0.12.2", 15 | "grunt-contrib-htmlmin": "^0.4.0", 16 | "grunt-contrib-imagemin": "^1.0.0", 17 | <% if (testFramework === 'jasmine') { -%> 18 | "grunt-contrib-jasmine": "^0.8.2", 19 | <% } -%> 20 | "grunt-contrib-uglify": "^0.8.0", 21 | "grunt-contrib-watch": "^0.6.1", 22 | "grunt-eslint": "^17.0.0", 23 | "grunt-filerev": "^2.2.0", 24 | <% if (testFramework === 'mocha') { -%> 25 | "grunt-mocha": "^0.4.12", 26 | <% } -%> 27 | "grunt-newer": "^1.1.0", 28 | "grunt-postcss": "^0.6.0", 29 | <% if (includeSass) { -%> 30 | "grunt-sass": "^1.0.0", 31 | <% } -%> 32 | "grunt-svgmin": "^2.0.1", 33 | "grunt-usemin": "^3.0.0", 34 | "grunt-wiredep": "^2.0.0", 35 | "jit-grunt": "^0.9.1", 36 | "time-grunt": "^1.1.0" 37 | }, 38 | "engines": { 39 | "node": ">=0.10.0" 40 | }, 41 | "scripts": { 42 | "test": "grunt test" 43 | }, 44 | "eslintConfig": { 45 | "extends": [ 46 | "eslint:recommended" 47 | ], 48 | "env": { 49 | "node": true, 50 | "browser": true<% if (useBabel) { %>, 51 | "es6": true<% } if (includeJQuery) { %>, 52 | "jquery": true<% } if (testFramework === 'mocha') { %>, 53 | "mocha": true<% } else if (testFramework === 'jasmine') { %>, 54 | "jasmine": true<% } %> 55 | }, 56 | "rules": { 57 | "quotes": [ 58 | 2, 59 | "single" 60 | ], 61 | "indent": [ 62 | 2, 63 | 2 64 | ] 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | * [Compass](compass.md) 4 | * [Assemble](assemble.md) 5 | 6 | *We welcome additional recipes for common use-cases not covered by this generator.* 7 | 8 | ## Tips for writing a recipe 9 | 10 | ### 1. Use the `master` branch of the generator 11 | 12 | The recipe should be up-to-date as much as possible. If you haven't already, clone the generator and link it: 13 | 14 | ```sh 15 | $ git clone https://github.com/yeoman/generator-webapp 16 | $ cd generator-webapp 17 | $ npm link 18 | $ cd ../ 19 | ``` 20 | 21 | Now the `yo webapp` command will use that version of the generator. To make sure this is actually true: 22 | 23 | ```sh 24 | $ npm ls -g generator-webapp 25 | # you should get something like 26 | /usr/local/lib 27 | └── generator-webapp@0.1.0 -> /Users/username/generator-webapp 28 | ``` 29 | 30 | To update the generator, all you need to do is: 31 | 32 | ```sh 33 | $ cd generator-webapp 34 | $ git pull origin master 35 | $ npm install 36 | $ cd ../ 37 | ``` 38 | 39 | ### 2. Create a test project 40 | 41 | Writing a recipe without actually testing it is very hard and error-prone, you should create a test project. Let's say you're writing a recipe for [Stylus](http://learnboost.github.io/stylus/): 42 | 43 | ```sh 44 | $ mkdir recipe-stylus && cd $_ 45 | $ yo webapp 46 | # select all options 47 | ``` 48 | 49 | ### 3. Track changes 50 | 51 | You should now create a Git repository and commit everything, this will allow you to see exactly which changes are required to implement Stylus: 52 | 53 | ```sh 54 | $ git init 55 | $ git add . 56 | $ git commit -m "Initial commit" 57 | ``` 58 | 59 | ### 4. Get your feature working 60 | 61 | Do whatever is necessary to get Stylus working, install required grunt plugins, change `Gruntfile.js`, update styles etc. 62 | 63 | ### 5. Write the recipe 64 | 65 | After you've completed a minimal set of changes to implement Stylus, run `git diff` and use the output to write the recipe. 66 | -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var helpers = require('yeoman-generator').test; 4 | var assert = require('yeoman-assert'); 5 | 6 | describe('bootstrap', function () { 7 | describe('general', function () { 8 | before(function (done) { 9 | helpers.run(path.join(__dirname, '../app')) 10 | .inDir(path.join(__dirname, '.tmp')) 11 | .withOptions({'skip-install': true}) 12 | .withPrompts({features: [ 13 | 'includeBootstrap' 14 | ]}) 15 | .on('end', done); 16 | }); 17 | 18 | it('adds the Bootstrap dependency', function () { 19 | assert.fileContent('bower.json', 'bootstrap'); 20 | }); 21 | 22 | it('doesn\'t explicitly add the jQuery dependency', function () { 23 | assert.noFileContent('bower.json', 'jquery'); 24 | }); 25 | 26 | it('adds Bootstrap paths to Gruntfile.js', function () { 27 | assert.fileContent('Gruntfile.js', 'bootstrap'); 28 | }); 29 | 30 | it('adds the HTML description', function () { 31 | assert.fileContent('app/index.html', 'Bootstrap'); 32 | }); 33 | }); 34 | 35 | describe('with Sass', function () { 36 | before(function (done) { 37 | helpers.run(path.join(__dirname, '../app')) 38 | .inDir(path.join(__dirname, '.tmp')) 39 | .withOptions({'skip-install': true}) 40 | .withPrompts({features: [ 41 | 'includeSass', 42 | 'includeBootstrap' 43 | ]}) 44 | .on('end', done); 45 | }); 46 | 47 | it('uses Bootstrap Sass', function () { 48 | assert.fileContent('bower.json', '"bootstrap-sass"'); 49 | assert.fileContent('Gruntfile.js', '/bootstrap-sass/'); 50 | }); 51 | }); 52 | 53 | describe('without Sass', function () { 54 | before(function (done) { 55 | helpers.run(path.join(__dirname, '../app')) 56 | .inDir(path.join(__dirname, '.tmp')) 57 | .withOptions({'skip-install': true}) 58 | .withPrompts({features: [ 59 | 'includeBootstrap' 60 | ]}) 61 | .on('end', done); 62 | }); 63 | 64 | it('uses regular Bootstrap', function () { 65 | assert.fileContent('bower.json', '"bootstrap"'); 66 | assert.fileContent('Gruntfile.js', '/bootstrap/'); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /app/templates/main.scss: -------------------------------------------------------------------------------- 1 | <% if (enableFlexbox) { -%> 2 | $enable-flex: true; 3 | <% } -%> 4 | 5 | <% if (includeFontAwesome) { -%> 6 | $fa-font-path: "../bower_components/components-font-awesome/fonts"; 7 | <% } -%> 8 | 9 | <% if (includeOcticons) { -%> 10 | $octicons-font-path: "../bower_components/octicons/octicons"; 11 | <% } -%> 12 | 13 | // bower:scss 14 | @import "bootstrap/scss/bootstrap.scss"; 15 | 16 | <% if (includeFontAwesome) { -%> 17 | @import "components-font-awesome/scss/font-awesome.scss"; 18 | <% } -%> 19 | 20 | <% if (includeOcticons) { -%> 21 | @import "octicons/octicons/octicons.scss"; 22 | <% } -%> 23 | // endbower 24 | 25 | .browsehappy { 26 | margin: 0.2em 0; 27 | background: #ccc; 28 | color: #000; 29 | padding: 0.2em 0; 30 | } 31 | 32 | /* Space out content a bit */ 33 | body { 34 | padding-top: 20px; 35 | padding-bottom: 20px; 36 | } 37 | 38 | /* Everything but the jumbotron gets side spacing for mobile first views */ 39 | .header, 40 | .marketing, 41 | .footer { 42 | padding-left: 15px; 43 | padding-right: 15px; 44 | } 45 | 46 | /* Custom page header */ 47 | .header { 48 | border-bottom: 1px solid #e5e5e5; 49 | 50 | /* Make the masthead heading the same height as the navigation */ 51 | h3 { 52 | margin-top: 0; 53 | margin-bottom: 0; 54 | line-height: 40px; 55 | padding-bottom: 19px; 56 | } 57 | } 58 | 59 | /* Custom page footer */ 60 | .footer { 61 | padding-top: 19px; 62 | color: #777; 63 | border-top: 1px solid #e5e5e5; 64 | } 65 | 66 | .container-narrow > hr { 67 | margin: 30px 0; 68 | } 69 | 70 | /* Main marketing message and sign up button */ 71 | .jumbotron { 72 | text-align: center; 73 | border-bottom: 1px solid #e5e5e5; 74 | .btn { 75 | font-size: 21px; 76 | padding: 14px 24px; 77 | } 78 | } 79 | 80 | /* Supporting marketing content */ 81 | .marketing { 82 | margin: 40px 0; 83 | p + h4 { 84 | margin-top: 28px; 85 | } 86 | } 87 | 88 | /* Responsive: Portrait tablets and up */ 89 | @media screen and (min-width: 768px) { 90 | .container { 91 | max-width: 730px; 92 | } 93 | 94 | /* Remove the padding we set earlier */ 95 | .header, 96 | .marketing, 97 | .footer { 98 | padding-left: 0; 99 | padding-right: 0; 100 | } 101 | 102 | /* Space out the masthead */ 103 | .header { 104 | margin-bottom: 30px; 105 | } 106 | 107 | /* Remove the bottom border on the jumbotron for visual effect */ 108 | .jumbotron { 109 | border-bottom: 0; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/templates/main.css: -------------------------------------------------------------------------------- 1 | <% if (includeBootstrap) { -%> 2 | .browsehappy { 3 | margin: 0.2em 0; 4 | background: #ccc; 5 | color: #000; 6 | padding: 0.2em 0; 7 | } 8 | 9 | /* Space out content a bit */ 10 | body { 11 | padding-top: 20px; 12 | padding-bottom: 20px; 13 | } 14 | 15 | /* Everything but the jumbotron gets side spacing for mobile first views */ 16 | .header, 17 | .marketing, 18 | .footer { 19 | padding-left: 15px; 20 | padding-right: 15px; 21 | } 22 | 23 | /* Custom page header */ 24 | .header { 25 | border-bottom: 1px solid #e5e5e5; 26 | } 27 | 28 | /* Make the masthead heading the same height as the navigation */ 29 | .header h3 { 30 | margin-top: 0; 31 | margin-bottom: 0; 32 | line-height: 40px; 33 | padding-bottom: 19px; 34 | } 35 | 36 | /* Custom page footer */ 37 | .footer { 38 | padding-top: 19px; 39 | color: #777; 40 | border-top: 1px solid #e5e5e5; 41 | } 42 | 43 | .container-narrow > hr { 44 | margin: 30px 0; 45 | } 46 | 47 | /* Main marketing message and sign up button */ 48 | .jumbotron { 49 | text-align: center; 50 | border-bottom: 1px solid #e5e5e5; 51 | } 52 | 53 | .jumbotron .btn { 54 | font-size: 21px; 55 | padding: 14px 24px; 56 | } 57 | 58 | /* Supporting marketing content */ 59 | .marketing { 60 | margin: 40px 0; 61 | } 62 | 63 | .marketing p + h4 { 64 | margin-top: 28px; 65 | } 66 | 67 | /* Responsive: Portrait tablets and up */ 68 | @media screen and (min-width: 768px) { 69 | .container { 70 | max-width: 730px; 71 | } 72 | 73 | /* Remove the padding we set earlier */ 74 | .header, 75 | .marketing, 76 | .footer { 77 | padding-left: 0; 78 | padding-right: 0; 79 | } 80 | 81 | /* Space out the masthead */ 82 | .header { 83 | margin-bottom: 30px; 84 | } 85 | 86 | /* Remove the bottom border on the jumbotron for visual effect */ 87 | .jumbotron { 88 | border-bottom: 0; 89 | } 90 | } 91 | <% } else { -%> 92 | body { 93 | background: #fafafa; 94 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 95 | color: #333; 96 | } 97 | 98 | .hero-unit { 99 | margin: 50px auto 0 auto; 100 | width: 300px; 101 | font-size: 18px; 102 | font-weight: 200; 103 | line-height: 30px; 104 | background-color: #eee; 105 | border-radius: 6px; 106 | padding: 60px; 107 | } 108 | 109 | .hero-unit h1 { 110 | font-size: 60px; 111 | line-height: 1; 112 | letter-spacing: -1px; 113 | } 114 | 115 | .browsehappy { 116 | margin: 0.2em 0; 117 | background: #ccc; 118 | color: #000; 119 | padding: 0.2em 0; 120 | } 121 | <% } -%> 122 | -------------------------------------------------------------------------------- /docs/recipes/compass.md: -------------------------------------------------------------------------------- 1 | # Adding Compass 2 | 3 | This recipe demonstrates how to add [Compass](http://compass-style.org/) to your 4 | existing Sass setup. 5 | 6 | ## Steps 7 | 8 | ### 1. Install dependencies 9 | 10 | Install the Compass Ruby gem and the grunt plugin: 11 | 12 | ```sh 13 | gem install compass 14 | npm install --save-dev grunt-contrib-compass 15 | ``` 16 | 17 | **Note**: `gem install`ing is global, so it doesn't matter where you're running the command from, but `npm install` should be run from inside the project directory. 18 | 19 | ### 2. Replace the `sass` task with `compass` 20 | 21 | Replace: 22 | 23 | ```js 24 | sass: { 25 | options: { 26 | loadPath: 'bower_components' 27 | }, 28 | dist: { 29 | files: [{ 30 | expand: true, 31 | cwd: '<%= config.app %>/styles', 32 | src: ['*.{scss,sass}'], 33 | dest: '.tmp/styles', 34 | ext: '.css' 35 | }] 36 | }, 37 | server: { 38 | files: [{ 39 | expand: true, 40 | cwd: '<%= config.app %>/styles', 41 | src: ['*.{scss,sass}'], 42 | dest: '.tmp/styles', 43 | ext: '.css' 44 | }] 45 | } 46 | }, 47 | ``` 48 | 49 | with: 50 | 51 | ```js 52 | compass: { 53 | options: { 54 | sassDir: '<%= config.app %>/styles', 55 | cssDir: '.tmp/styles', 56 | imagesDir: '<%= config.app %>/images', 57 | javascriptsDir: '<%= config.app %>/scripts', 58 | fontsDir: '<%= config.app %>/fonts', 59 | generatedImagesDir: '.tmp/images/generated', 60 | importPath: 'bower_components', 61 | httpImagesPath: '../images', 62 | httpGeneratedImagesPath: '../images/generated', 63 | httpFontsPath: '../fonts', 64 | relativeAssets: false, 65 | assetCacheBuster: false 66 | }, 67 | dist: { 68 | options: { 69 | generatedImagesDir: '<%= config.dist %>/images/generated' 70 | } 71 | }, 72 | server: { 73 | options: { 74 | debugInfo: true 75 | } 76 | } 77 | }, 78 | ``` 79 | 80 | * Now you can use asset functions like `font-url()` and `image-url()` in your Sass. 81 | * This solution uses relative paths, but you can edit values of `http*` options to absolute paths, just make sure not to break grunt-usemin. 82 | 83 | ### 3. Update other tasks 84 | 85 | #### `watch` 86 | 87 | ```diff 88 | -sass: { 89 | +compass: { 90 | files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], 91 | - tasks: ['sass:server', 'autoprefixer'] 92 | + tasks: ['compass:server', 'autoprefixer'] 93 | }, 94 | ``` 95 | 96 | #### `concurrent` 97 | 98 | ```diff 99 | concurrent: { 100 | server: [ 101 | - 'sass:server', 102 | + 'compass:server', 103 | 'copy:styles' 104 | ], 105 | test: [ 106 | 'copy:styles' 107 | ], 108 | dist: [ 109 | - 'sass', 110 | + 'compass', 111 | 'copy:styles', 112 | 'imagemin', 113 | 'svgmin' 114 | ] 115 | } 116 | ``` 117 | 118 | ### 4. Use Compass features 119 | 120 | No `@import "compass"` needed. 121 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Bootstrap 4 web app generator [![Build Status](https://secure.travis-ci.org/yeoman/generator-webapp.svg?branch=master)](http://travis-ci.org/yeoman/generator-webapp) 2 | 3 | [Yeoman](http://yeoman.io) generator that scaffolds out a front-end Bootstrap 4 web app. 4 | 5 | ![Run the Bootstrap 4 generator](yeoman.png) 6 | 7 | ## Features 8 | 9 | * Bootstrap 4 10 | * Flexbox support (Optional) 11 | * Font Awesome (Optional) 12 | * Octicons (Optional) 13 | * CSS Autoprefixing 14 | * Built-in preview server with LiveReload 15 | * Automagically compile ES6 (with Babel) & Sass 16 | * Automagically lint your scripts 17 | * Automagically wire up your Bower components with [grunt-wiredep](#third-party-dependencies). 18 | * Awesome Image Optimization (via OptiPNG, pngquant, jpegtran and gifsicle) 19 | * Mocha Unit Testing with PhantomJS 20 | 21 | 22 | For more information on what `generator-bootstrap4` can do for you, take a look at the [Grunt tasks](https://github.com/bassjobsen/generator-bootstrap4/blob/master/app/templates/_package.json) used in our `package.json`. 23 | 24 | 25 | ## Getting Started 26 | 27 | - Install: `npm install -g generator-bootstrap4` 28 | - Run: `yo bootstrap4` 29 | - Run `grunt` for building and `grunt serve` for preview[\*](#grunt-serve-note). `--allow-remote` option for remote access. 30 | 31 | 32 | #### Third-Party Dependencies 33 | 34 | *(HTML/CSS/JS/Images/etc)* 35 | 36 | Third-party dependencies are managed with [grunt-wiredep](https://github.com/stephenplusplus/grunt-wiredep). Add new dependencies using **Bower** and then run the **Grunt** task to load them: 37 | 38 | ```sh 39 | $ bower install --save jquery 40 | $ grunt wiredep 41 | ``` 42 | 43 | This works if the package author has followed the [Bower spec](https://github.com/bower/bower.json-spec). If the files are not automatically added to your source code, check with the package's repo for support and/or file an issue with them to have it updated. 44 | 45 | To manually add dependencies, `bower install --save depName` to get the files, then add a `script` or `style` tag to your `index.html` or another appropriate place. 46 | 47 | The components are installed in the root of the project at `/bower_components`. To reference them from index.html, use `src="bower_components"` or `src="/bower_components"`. Treat the `bower_components` directory as if it was a sibling to `index.html`. 48 | 49 | *Testing Note*: a project checked into source control and later checked out needs to have `bower install` run from the `test` folder as well as from the project root. 50 | 51 | 52 | #### Grunt Serve Note 53 | 54 | Note: `grunt server` was used for previewing in earlier versions of the project, and has since been deprecated in favor of `grunt serve`. 55 | 56 | 57 | ## Docs 58 | 59 | We have [recipes](docs/recipes) for integrating other popular technologies like Compass. 60 | 61 | 62 | ## Options 63 | 64 | * `--skip-install` 65 | 66 | Skips the automatic execution of `bower` and `npm` after scaffolding has finished. 67 | 68 | * `--test-framework=` 69 | 70 | Either `mocha` or `jasmine`. Defaults to `mocha`. 71 | 72 | * `--no-babel` 73 | 74 | Turn off [Babel](http://babeljs.io/) support. 75 | 76 | ## Contribute 77 | 78 | See the [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md). 79 | 80 | Note: We are regularly asked whether we can add or take away features. If a change is good enough to have a positive impact on all users, we are happy to consider it. 81 | 82 | 83 | ## License 84 | 85 | [BSD license](http://opensource.org/licenses/bsd-license.php) 86 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= appname %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 |
27 |
28 | 33 |

<%= appname %>

34 |
35 | 36 |
37 |

'Allo, 'Allo!

38 |

Always a pleasure scaffolding your apps.

39 |

Splendid!<% if (includeFontAwesome) { %> <% } %>

40 |
41 | 42 |
43 | 44 |
45 |
46 |

Bootstrap 4

47 |

Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.

48 |
49 |
50 |
51 |
52 |

Sass

53 |

Sass is a mature, stable, and powerful professional grade CSS extension language.

54 |
55 |
56 | <% if (includeFontAwesome) { %>
57 |
58 |

Font Awesome

59 |

Font Awesome gives you scalable vector icons that can instantly be customized — size, color, drop shadow, and anything that can be done with the power of CSS.

60 |
61 |
<% } %> 62 | <% if (includeOcticons) { %>
63 |
64 |

Github Octicons

65 |

Way back in 2012, social coding community GitHub launched its own icon font called Octicons. GitHub hasmade them available for anyone and everyone to download and use.

66 |
67 |
<% } %> 68 |
69 | 70 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | <% if (includeBootstrap) { %> 81 | 82 | <% bsPlugins.forEach(function (plugin) { -%> 83 | 84 | <% }) -%> 85 | 86 | <% } %> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/recipes/assemble.md: -------------------------------------------------------------------------------- 1 | # Adding Assemble 2 | 3 | This recipe demonstrates how to add [Assemble](http://assemble.io) to your existing setup. 4 | 5 | 6 | ## Steps 7 | 8 | ### 1. Install dependencies 9 | 10 | Install the grunt plugin: 11 | 12 | ``` 13 | $ npm install --save-dev assemble 14 | ``` 15 | 16 | 17 | ### 2. Load assemble 18 | 19 | ```diff 20 | module.exports = function (grunt) { 21 | + // Load assemble.io 22 | + grunt.loadNpmTasks('assemble'); 23 | 24 | // Time how long tasks take. Can help when optimizing build times 25 | require('time-grunt')(grunt); 26 | ``` 27 | 28 | ### 3. Update `watch` task 29 | 30 | ```diff 31 | // Watches files for changes and runs tasks based on the changed files 32 | watch: { 33 | bower: { 34 | files: ['bower.json'], 35 | tasks: ['wiredep'] 36 | }, 37 | js: { 38 | files: ['<%= config.app %>/scripts/{,*/}*.js'], 39 | tasks: ['jshint'], 40 | }, 41 | jstest: { 42 | files: ['test/spec/{,*/}*.js'], 43 | tasks: ['test:watch'] 44 | }, 45 | gruntfile: { 46 | files: ['Gruntfile.js'] 47 | }, 48 | sass: { 49 | files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], 50 | tasks: ['sass:server', 'autoprefixer'] 51 | }, 52 | styles: { 53 | files: ['<%= config.app %>/styles/{,*/}*.css'], 54 | tasks: ['newer:copy:styles', 'autoprefixer'] 55 | }, 56 | + assemble: { 57 | + files: ['<%= config.app %>/templates/**/*.hbs'], 58 | + tasks: ['newer:assemble'] 59 | + } 60 | }, 61 | ``` 62 | 63 | ### 4. Update `livereload` task 64 | 65 | ```diff 66 | livereload: { 67 | options: { 68 | files: [ 69 | - '<%= config.app %>/{,*/}*.html', 70 | + '.tmp/*.html', 71 | '.tmp/styles/{,*/}*.css', 72 | '<%= config.app %>/images/{,*/}*', 73 | '<%= config.app %>/scripts/{,*/}*.js' 74 | ], 75 | port: 9000, 76 | server: { 77 | baseDir: ['.tmp', config.app], 78 | routes: { 79 | '/bower_components': './bower_components' 80 | } 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ### 5. Update `wiredep` task 87 | 88 | ```diff 89 | // Automatically inject Bower components into the default handlebars template file 90 | wiredep: { 91 | app: { 92 | ignorePath: /^\/|\.\.\//, 93 | - src: ['<%= config.app %>/index.html'], 94 | + src: ['<%= config.app %>/templates/layouts/default.hbs'], 95 | exclude: ['bower_components/bootstrap-sass/assets/javascripts/bootstrap.js'] 96 | }, 97 | sass: { 98 | src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], 99 | ignorePath: /(\.\.\/){1,2}bower_components\// 100 | } 101 | }, 102 | ``` 103 | 104 | ### 6. Update `useminPrepare` task 105 | 106 | ```diff 107 | useminPrepare: { 108 | options: { 109 | dest: '<%= config.dist %>' 110 | }, 111 | - html: '<%= config.app %>/index.html' 112 | + html: '.tmp/index.html' 113 | }, 114 | ``` 115 | 116 | ### 7. Update `copy` task 117 | 118 | ```diff 119 | // Copies remaining files to places other tasks can use 120 | copy: { 121 | dist: { 122 | files: [{ 123 | expand: true, 124 | dot: true, 125 | cwd: '<%= config.app %>', 126 | dest: '<%= config.dist %>', 127 | src: [ 128 | '*.{ico,png,txt}', 129 | 'images/{,*/}*.webp', 130 | - '{,*/}*.html', 131 | 'fonts/{,*/}*.*' 132 | ] 133 | + }, { 134 | + expand: true, 135 | + dot: true, 136 | + cwd: '.tmp', 137 | + dest: '<%= config.dist %>', 138 | + src: '{,*/}*.html' 139 | + }, { 140 | expand: true, 141 | dot: true, 142 | cwd: '.', 143 | dest: '<%= config.dist %>', 144 | src: 'bower_components/bootstrap-sass/assets/fonts/bootstrap/*' 145 | }] 146 | } 147 | }, 148 | ``` 149 | 150 | ### 8. Create `assemble` task 151 | 152 | ```diff 153 | // Empties folders to start fresh 154 | clean: { 155 | dist: { 156 | files: [{ 157 | dot: true, 158 | src: [ 159 | '.tmp', 160 | '<%= config.dist %>/*', 161 | '!<%= config.dist %>/.git*' 162 | ] 163 | }] 164 | }, 165 | server: '.tmp' 166 | }, 167 | 168 | + // Create html pages using assemble 169 | + assemble: { 170 | + options: { 171 | + flatten: true, 172 | + layout: '<%= config.app %>/templates/layouts/default.hbs', 173 | + partials: ['<%= config.app %>/templates/partials/**/*.hbs'], 174 | + }, 175 | + pages: { 176 | + files: { 177 | + '.tmp/': ['<%= config.app %>/templates/pages/**/*.hbs'] 178 | + } 179 | + } 180 | + }, 181 | 182 | // Make sure code styles are up to par and there are no obvious mistakes 183 | jshint: { 184 | options: { 185 | jshintrc: '.jshintrc', 186 | reporter: require('jshint-stylish') 187 | }, 188 | all: [ 189 | 'Gruntfile.js', 190 | '<%= config.app %>/scripts/{,*/}*.js', 191 | '!<%= config.app %>/scripts/vendor/*', 192 | 'test/spec/{,*/}*.js' 193 | ] 194 | }, 195 | ``` 196 | 197 | ### 9. Add `assemble` task to existing tasks 198 | 199 | #### `serve` 200 | 201 | ```diff 202 | grunt.registerTask('serve', 'start the server and preview your app', function (target) { 203 | 204 | if (target === 'dist') { 205 | return grunt.task.run(['build', 'browserSync:dist']); 206 | } 207 | 208 | grunt.task.run([ 209 | 'clean:server', 210 | + 'assemble', 211 | 'wiredep', 212 | 'concurrent:server', 213 | 'autoprefixer', 214 | 'browserSync:livereload', 215 | 'watch' 216 | ]); 217 | }); 218 | ``` 219 | 220 | #### `build` 221 | ```diff 222 | grunt.registerTask('build', [ 223 | 'clean:dist', 224 | + 'assemble', 225 | 'wiredep', 226 | 'useminPrepare', 227 | 'concurrent:dist', 228 | 'autoprefixer', 229 | 'concat', 230 | 'cssmin', 231 | 'uglify', 232 | 'copy:dist', 233 | 'modernizr', 234 | 'filerev', 235 | 'usemin', 236 | 'htmlmin' 237 | ]); 238 | ``` 239 | 240 | #### `test` 241 | 242 | ```diff 243 | grunt.registerTask('test', function (target) { 244 | if (target !== 'watch') { 245 | grunt.task.run([ 246 | 'clean:server', 247 | + 'assemble', 248 | 'concurrent:test', 249 | 'autoprefixer' 250 | ]); 251 | } 252 | 253 | grunt.task.run([ 254 | 'browserSync:test', 255 | 'mocha' 256 | ]); 257 | }); 258 | ``` 259 | 260 | ### 10. Create assemble needed directories and files 261 | 262 | ``` 263 | |-- /app 264 | | |-- /templates 265 | | | |-- /layouts 266 | | | | |-- default.hbs 267 | | | |-- /pages 268 | | | | |-- index.hbs 269 | | | |-- /partials 270 | ``` 271 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var generators = require('yeoman-generator'); 3 | var yosay = require('yosay'); 4 | var chalk = require('chalk'); 5 | var wiredep = require('wiredep'); 6 | var mkdirp = require('mkdirp'); 7 | var _s = require('underscore.string'); 8 | 9 | module.exports = generators.Base.extend({ 10 | constructor: function () { 11 | var testLocal; 12 | 13 | generators.Base.apply(this, arguments); 14 | 15 | this.option('skip-welcome-message', { 16 | desc: 'Skips the welcome message', 17 | type: Boolean 18 | }); 19 | 20 | this.option('skip-install-message', { 21 | desc: 'Skips the message after the installation of dependencies', 22 | type: Boolean 23 | }); 24 | 25 | // setup the test-framework property, Gruntfile template will need this 26 | this.option('test-framework', { 27 | desc: 'Test framework to be invoked', 28 | type: String, 29 | defaults: 'mocha' 30 | }); 31 | 32 | this.option('babel', { 33 | desc: 'Use Babel', 34 | type: Boolean, 35 | defaults: true 36 | }); 37 | 38 | if (this.options['test-framework'] === 'mocha') { 39 | testLocal = require.resolve('generator-mocha/generators/app/index.js'); 40 | } else if (this.options['test-framework'] === 'jasmine') { 41 | testLocal = require.resolve('generator-jasmine/generators/app/index.js'); 42 | } 43 | 44 | this.composeWith(this.options['test-framework'] + ':app', { 45 | options: { 46 | 'skip-install': this.options['skip-install'] 47 | } 48 | }, { 49 | local: testLocal 50 | }); 51 | }, 52 | 53 | initializing: function () { 54 | this.pkg = require('../package.json'); 55 | }, 56 | 57 | askFor: function () { 58 | var done = this.async(); 59 | 60 | if (!this.options['skip-welcome-message']) { 61 | this.log(yosay('\'Allo \'allo! Out of the box I include Bootstrap 4, jQuery, and a Gruntfile to build your app.')); 62 | } 63 | 64 | var prompts = [{ 65 | type: 'checkbox', 66 | name: 'features', 67 | message: 'What more would you like?', 68 | choices: [{ 69 | name: 'Font Awesome', 70 | value: 'includeFontAwesome', 71 | checked: false 72 | }, { 73 | name: 'Octicons', 74 | value: 'includeOcticons', 75 | checked: false 76 | }] 77 | }, { 78 | type: 'confirm', 79 | name: 'enableFlexbox', 80 | message: 'Would you like to enable flexbox (reduced browser and device support)?', 81 | default: false 82 | }]; 83 | 84 | this.prompt(prompts, function (answers) { 85 | var features = answers.features; 86 | 87 | function hasFeature(feat) { 88 | return features && features.indexOf(feat) !== -1; 89 | } 90 | 91 | this.includeSass = true; //hasFeature('includeSass'); 92 | this.includeBootstrap = true; //hasFeature('includeBootstrap'); 93 | this.includeModernizr = false; //hasFeature('includeModernizr'); 94 | this.includeFontAwesome = hasFeature('includeFontAwesome'); 95 | this.includeOcticons = hasFeature('includeOcticons'); 96 | this.enableFlexbox = answers.enableFlexbox; 97 | 98 | done(); 99 | }.bind(this)); 100 | }, 101 | 102 | writing: { 103 | gruntfile: function () { 104 | this.fs.copyTpl( 105 | this.templatePath('Gruntfile.js'), 106 | this.destinationPath('Gruntfile.js'), 107 | { 108 | pkg: this.pkg, 109 | includeSass: this.includeSass, 110 | includeBootstrap: this.includeBootstrap, 111 | testFramework: this.options['test-framework'], 112 | useBabel: this.options['babel'] 113 | } 114 | ); 115 | }, 116 | 117 | packageJSON: function () { 118 | this.fs.copyTpl( 119 | this.templatePath('_package.json'), 120 | this.destinationPath('package.json'), 121 | { 122 | includeSass: this.includeSass, 123 | enableFlexbox: this.enableFlexbox, 124 | includeJQuery: this.includeBootstrap || this.includeJQuery, 125 | includeFontAwesome: this.includeFontAwesome, 126 | includeOcticons: this.includeOcticons, 127 | testFramework: this.options['test-framework'], 128 | useBabel: this.options['babel'] 129 | } 130 | ) 131 | }, 132 | 133 | git: function () { 134 | this.fs.copy( 135 | this.templatePath('gitignore'), 136 | this.destinationPath('.gitignore') 137 | ); 138 | 139 | this.fs.copy( 140 | this.templatePath('gitattributes'), 141 | this.destinationPath('.gitattributes') 142 | ); 143 | }, 144 | 145 | bower: function () { 146 | var bowerJson = { 147 | name: _s.slugify(this.appname), 148 | private: true, 149 | dependencies: {} 150 | }; 151 | 152 | if (this.includeBootstrap) { 153 | bowerJson.dependencies['bootstrap'] = 'https://github.com/twbs/bootstrap.git#v4-dev'; 154 | bowerJson.dependencies['jquery'] = '~2.1.4'; 155 | } 156 | 157 | if (this.includeFontAwesome) { 158 | bowerJson.dependencies['components-font-awesome'] = '~4.4'; 159 | } 160 | if (this.includeOcticons) { 161 | bowerJson.dependencies['octicons'] = '~3.3.0'; 162 | bowerJson.overrides = { 163 | 'octicons': { 164 | 'main': [ 165 | 'octicons/octicons.scss' 166 | ] 167 | } 168 | }; 169 | } 170 | 171 | this.fs.writeJSON('bower.json', bowerJson); 172 | this.fs.copy( 173 | this.templatePath('bowerrc'), 174 | this.destinationPath('.bowerrc') 175 | ); 176 | }, 177 | 178 | editorConfig: function () { 179 | this.fs.copy( 180 | this.templatePath('editorconfig'), 181 | this.destinationPath('.editorconfig') 182 | ); 183 | }, 184 | 185 | scripts: function () { 186 | this.fs.copy( 187 | this.templatePath('main.js'), 188 | this.destinationPath('app/scripts/main.js') 189 | ); 190 | }, 191 | 192 | styles: function () { 193 | var stylesheet; 194 | 195 | if (this.includeSass) { 196 | stylesheet = 'main.scss'; 197 | } else { 198 | stylesheet = 'main.css'; 199 | } 200 | 201 | this.fs.copyTpl( 202 | this.templatePath(stylesheet), 203 | this.destinationPath('app/styles/' + stylesheet), 204 | { 205 | includeBootstrap: this.includeBootstrap, 206 | includeFontAwesome: this.includeFontAwesome, 207 | includeOcticons: this.includeOcticons, 208 | enableFlexbox: this.enableFlexbox 209 | } 210 | ) 211 | }, 212 | 213 | html: function () { 214 | var bsPath; 215 | 216 | // path prefix for Bootstrap JS files 217 | bsPath = '/bower_components/bootstrap/js/dist/'; 218 | 219 | this.fs.copyTpl( 220 | this.templatePath('index.html'), 221 | this.destinationPath('app/index.html'), 222 | { 223 | appname: this.appname, 224 | includeSass: this.includeSass, 225 | includeBootstrap: this.includeBootstrap, 226 | includeFontAwesome: this.includeFontAwesome, 227 | includeOcticons: this.includeOcticons, 228 | enableFlexbox: this.enableFlexbox, 229 | bsPath: bsPath, 230 | bsPlugins: [ 231 | 'alert', 232 | 'button', 233 | 'carousel', 234 | 'collapse', 235 | 'dropdown', 236 | 'modal', 237 | 'scrollspy', 238 | 'tab', 239 | 'tooltip', 240 | 'popover', 241 | 'util' 242 | ] 243 | } 244 | ); 245 | }, 246 | 247 | icons: function () { 248 | this.fs.copy( 249 | this.templatePath('favicon.ico'), 250 | this.destinationPath('app/favicon.ico') 251 | ); 252 | 253 | this.fs.copy( 254 | this.templatePath('apple-touch-icon.png'), 255 | this.destinationPath('app/apple-touch-icon.png') 256 | ); 257 | }, 258 | 259 | robots: function () { 260 | this.fs.copy( 261 | this.templatePath('robots.txt'), 262 | this.destinationPath('app/robots.txt') 263 | ); 264 | }, 265 | 266 | misc: function () { 267 | mkdirp('app/images'); 268 | mkdirp('app/fonts'); 269 | } 270 | }, 271 | 272 | install: function () { 273 | this.installDependencies({ 274 | skipInstall: this.options['skip-install'], 275 | skipMessage: this.options['skip-install-message'] 276 | }); 277 | }, 278 | 279 | end: function () { 280 | var bowerJson = this.fs.readJSON(this.destinationPath('bower.json')); 281 | var howToInstall = 282 | '\nAfter running ' + 283 | chalk.yellow.bold('npm install & bower install') + 284 | ', inject your' + 285 | '\nfront end dependencies by running ' + 286 | chalk.yellow.bold('grunt wiredep') + 287 | '.'; 288 | 289 | if (this.options['skip-install']) { 290 | this.log(howToInstall); 291 | return; 292 | } 293 | 294 | // wire Bower packages to .html 295 | wiredep({ 296 | bowerJson: bowerJson, 297 | src: 'app/index.html', 298 | exclude: ['bootstrap.js'], 299 | ignorePath: /^(\.\.\/)*\.\./ 300 | }); 301 | 302 | if (this.includeSass) { 303 | // wire Bower packages to .scss 304 | wiredep({ 305 | bowerJson: bowerJson, 306 | src: 'app/styles/*.scss', 307 | ignorePath: /^(\.\.\/)+/ 308 | }); 309 | } 310 | } 311 | }); 312 | -------------------------------------------------------------------------------- /app/templates/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on <%= (new Date).toISOString().split('T')[0] %> using 2 | // <%= pkg.name %> <%= pkg.version %> 3 | 'use strict'; 4 | 5 | // # Globbing 6 | // for performance reasons we're only matching one level down: 7 | // 'test/spec/{,*/}*.js' 8 | // If you want to recursively match all subfolders, use: 9 | // 'test/spec/**/*.js' 10 | 11 | module.exports = function (grunt) { 12 | 13 | // Time how long tasks take. Can help when optimizing build times 14 | require('time-grunt')(grunt); 15 | 16 | // Automatically load required grunt tasks 17 | require('jit-grunt')(grunt, { 18 | useminPrepare: 'grunt-usemin' 19 | }); 20 | 21 | // Configurable paths 22 | var config = { 23 | app: 'app', 24 | dist: 'dist' 25 | }; 26 | 27 | // Define the configuration for all the tasks 28 | grunt.initConfig({ 29 | 30 | // Project settings 31 | config: config, 32 | 33 | // Watches files for changes and runs tasks based on the changed files 34 | watch: { 35 | bower: { 36 | files: ['bower.json'], 37 | tasks: ['wiredep'] 38 | },<% if (useBabel) { %> 39 | babel: { 40 | files: ['<%%= config.app %>/scripts/{,*/}*.js'], 41 | tasks: ['babel:dist'] 42 | }, 43 | babelTest: { 44 | files: ['test/spec/{,*/}*.js'], 45 | tasks: ['babel:test', 'test:watch'] 46 | },<% } else { %> 47 | js: { 48 | files: ['<%%= config.app %>/scripts/{,*/}*.js'], 49 | tasks: ['eslint'] 50 | }, 51 | jstest: { 52 | files: ['test/spec/{,*/}*.js'], 53 | tasks: ['test:watch'] 54 | },<% } %> 55 | gruntfile: { 56 | files: ['Gruntfile.js'] 57 | },<% if (includeSass) { %> 58 | sass: { 59 | files: ['<%%= config.app %>/styles/{,*/}*.{scss,sass}'], 60 | tasks: ['sass', 'postcss'] 61 | },<% } %> 62 | styles: { 63 | files: ['<%%= config.app %>/styles/{,*/}*.css'], 64 | tasks: ['newer:copy:styles', 'postcss'] 65 | } 66 | }, 67 | 68 | browserSync: { 69 | options: { 70 | notify: false, 71 | background: true, 72 | watchOptions: { 73 | ignored: '' 74 | } 75 | }, 76 | livereload: { 77 | options: { 78 | files: [ 79 | '<%%= config.app %>/{,*/}*.html', 80 | '.tmp/styles/{,*/}*.css', 81 | '<%%= config.app %>/images/{,*/}*',<% if (useBabel) { %> 82 | '.tmp/scripts/{,*/}*.js'<% } else { %> 83 | '<%%= config.app %>/scripts/{,*/}*.js'<% } %> 84 | ], 85 | port: 9000, 86 | server: { 87 | baseDir: ['.tmp', config.app], 88 | routes: { 89 | '/bower_components': './bower_components' 90 | } 91 | } 92 | } 93 | }, 94 | test: { 95 | options: { 96 | port: 9001, 97 | open: false, 98 | logLevel: 'silent', 99 | host: 'localhost', 100 | server: {<% if (testFramework === 'mocha') { %> 101 | baseDir: ['.tmp', './test', config.app],<% } else if (testFramework === 'jasmine') { %> 102 | baseDir: ['./'],<% } %> 103 | routes: { 104 | '/bower_components': './bower_components' 105 | } 106 | } 107 | } 108 | }, 109 | dist: { 110 | options: { 111 | background: false, 112 | server: '<%%= config.dist %>' 113 | } 114 | } 115 | }, 116 | 117 | // Empties folders to start fresh 118 | clean: { 119 | dist: { 120 | files: [{ 121 | dot: true, 122 | src: [ 123 | '.tmp', 124 | '<%%= config.dist %>/*', 125 | '!<%%= config.dist %>/.git*' 126 | ] 127 | }] 128 | }, 129 | server: '.tmp' 130 | }, 131 | 132 | // Make sure code styles are up to par and there are no obvious mistakes 133 | eslint: { 134 | target: [ 135 | 'Gruntfile.js', 136 | '<%%= config.app %>/scripts/{,*/}*.js', 137 | '!<%%= config.app %>/scripts/vendor/*', 138 | 'test/spec/{,*/}*.js' 139 | ] 140 | },<% if (testFramework === 'mocha') { %> 141 | 142 | // Mocha testing framework configuration options 143 | mocha: { 144 | all: { 145 | options: { 146 | run: true, 147 | urls: ['http://<%%= browserSync.test.options.host %>:<%%= browserSync.test.options.port %>/index.html'] 148 | } 149 | } 150 | },<% } else if (testFramework === 'jasmine') { %> 151 | 152 | // Jasmine testing framework configuration options 153 | jasmine: { 154 | all: { 155 | <% if (useBabel) { -%> 156 | src: '.tmp/scripts/{,*/}.js', 157 | <% } else { -%> 158 | src: '{<%%= config.app %>,.tmp}/scripts/{,*/}*.js', 159 | <% } -%> 160 | options: { 161 | vendor: [ 162 | // Your bower_components scripts 163 | ], 164 | <% if (useBabel) { -%> 165 | specs: '.tmp/spec/{,*/}*.js', 166 | <% } else { -%> 167 | specs: '{test,.tmp}/spec/{,*/}*.js', 168 | <% } -%> 169 | helpers: '{test,.tmp}/helpers/{,*/}*.js', 170 | host: 'http://<%%= browserSync.test.options.host %>:<%%= browserSync.test.options.port %>' 171 | } 172 | } 173 | },<% } %><% if (useBabel) { %> 174 | 175 | // Compiles ES6 with Babel 176 | babel: { 177 | options: { 178 | sourceMap: true 179 | }, 180 | dist: { 181 | files: [{ 182 | expand: true, 183 | cwd: '<%%= config.app %>/scripts', 184 | src: '{,*/}*.js', 185 | dest: '.tmp/scripts', 186 | ext: '.js' 187 | }] 188 | }, 189 | test: { 190 | files: [{ 191 | expand: true, 192 | cwd: 'test/spec', 193 | src: '{,*/}*.js', 194 | dest: '.tmp/spec', 195 | ext: '.js' 196 | }] 197 | } 198 | },<% } %><% if (includeSass) { %> 199 | 200 | // Compiles Sass to CSS and generates necessary files if requested 201 | sass: { 202 | options: { 203 | sourceMap: true, 204 | sourceMapEmbed: true, 205 | sourceMapContents: true, 206 | includePaths: ['.'] 207 | }, 208 | dist: { 209 | files: [{ 210 | expand: true, 211 | cwd: '<%%= config.app %>/styles', 212 | src: ['*.{scss,sass}'], 213 | dest: '.tmp/styles', 214 | ext: '.css' 215 | }] 216 | } 217 | },<% } %> 218 | 219 | postcss: { 220 | options: { 221 | map: true, 222 | processors: [ 223 | // Add vendor prefixed styles 224 | require('autoprefixer')({ 225 | browsers: [ 226 | 'Android 2.3', 227 | 'Android >= 4', 228 | 'Chrome >= 35', 229 | 'Firefox >= 31', 230 | // Note: Edge versions in Autoprefixer & Can I Use refer to the EdgeHTML rendering engine version, 231 | // NOT the Edge app version shown in Edge's "About" screen. 232 | // For example, at the time of writing, Edge 20 on an up-to-date system uses EdgeHTML 12. 233 | // See also https://github.com/Fyrd/caniuse/issues/1928 234 | 'Edge >= 12', 235 | 'Explorer >= 9', 236 | 'iOS >= 7', 237 | 'Opera >= 12', 238 | 'Safari >= 7.1' 239 | ] 240 | }) 241 | ] 242 | }, 243 | dist: { 244 | files: [{ 245 | expand: true, 246 | cwd: '.tmp/styles/', 247 | src: '{,*/}*.css', 248 | dest: '.tmp/styles/' 249 | }] 250 | } 251 | }, 252 | 253 | // Automatically inject Bower components into the HTML file 254 | wiredep: { 255 | app: { 256 | src: ['<%%= config.app %>/index.html'], 257 | <% if (includeBootstrap) { -%> 258 | exclude: ['bootstrap.js'], 259 | <% } -%> 260 | ignorePath: /^(\.\.\/)*\.\./ 261 | }<% if (includeSass) { %>, 262 | sass: { 263 | src: ['<%%= config.app %>/styles/{,*/}*.{scss,sass}'], 264 | ignorePath: /^(\.\.\/)+/ 265 | }<% } %> 266 | }, 267 | 268 | // Renames files for browser caching purposes 269 | filerev: { 270 | dist: { 271 | src: [ 272 | '<%%= config.dist %>/scripts/{,*/}*.js', 273 | '<%%= config.dist %>/styles/{,*/}*.css', 274 | '<%%= config.dist %>/images/{,*/}*.*', 275 | '<%%= config.dist %>/fonts/{,*/}*.*', 276 | '<%%= config.dist %>/*.{ico,png}' 277 | ] 278 | } 279 | }, 280 | 281 | // Reads HTML for usemin blocks to enable smart builds that automatically 282 | // concat, minify and revision files. Creates configurations in memory so 283 | // additional tasks can operate on them 284 | useminPrepare: { 285 | options: { 286 | dest: '<%%= config.dist %>' 287 | }, 288 | html: '<%%= config.app %>/index.html' 289 | }, 290 | 291 | // Performs rewrites based on rev and the useminPrepare configuration 292 | usemin: { 293 | options: { 294 | assetsDirs: [ 295 | '<%%= config.dist %>', 296 | '<%%= config.dist %>/images', 297 | '<%%= config.dist %>/styles' 298 | ] 299 | }, 300 | html: ['<%%= config.dist %>/{,*/}*.html'], 301 | css: ['<%%= config.dist %>/styles/{,*/}*.css'] 302 | }, 303 | 304 | // The following *-min tasks produce minified files in the dist folder 305 | imagemin: { 306 | dist: { 307 | files: [{ 308 | expand: true, 309 | cwd: '<%%= config.app %>/images', 310 | src: '{,*/}*.{gif,jpeg,jpg,png}', 311 | dest: '<%%= config.dist %>/images' 312 | }] 313 | } 314 | }, 315 | 316 | svgmin: { 317 | dist: { 318 | files: [{ 319 | expand: true, 320 | cwd: '<%%= config.app %>/images', 321 | src: '{,*/}*.svg', 322 | dest: '<%%= config.dist %>/images' 323 | }] 324 | } 325 | }, 326 | 327 | htmlmin: { 328 | dist: { 329 | options: { 330 | collapseBooleanAttributes: true, 331 | collapseWhitespace: true, 332 | conservativeCollapse: true, 333 | removeAttributeQuotes: true, 334 | removeCommentsFromCDATA: true, 335 | removeEmptyAttributes: true, 336 | removeOptionalTags: true, 337 | // true would impact styles with attribute selectors 338 | removeRedundantAttributes: false, 339 | useShortDoctype: true 340 | }, 341 | files: [{ 342 | expand: true, 343 | cwd: '<%%= config.dist %>', 344 | src: '{,*/}*.html', 345 | dest: '<%%= config.dist %>' 346 | }] 347 | } 348 | }, 349 | 350 | // By default, your `index.html`'s will take care 351 | // of minification. These next options are pre-configured if you do not 352 | // wish to use the Usemin blocks. 353 | // cssmin: { 354 | // dist: { 355 | // files: { 356 | // '<%%= config.dist %>/styles/main.css': [ 357 | // '.tmp/styles/{,*/}*.css', 358 | // '<%%= config.app %>/styles/{,*/}*.css' 359 | // ] 360 | // } 361 | // } 362 | // }, 363 | // uglify: { 364 | // dist: { 365 | // files: { 366 | // '<%%= config.dist %>/scripts/scripts.js': [ 367 | // '<%%= config.dist %>/scripts/scripts.js' 368 | // ] 369 | // } 370 | // } 371 | // }, 372 | // concat: { 373 | // dist: {} 374 | // }, 375 | 376 | // Copies remaining files to places other tasks can use 377 | copy: { 378 | dist: { 379 | files: [{ 380 | expand: true, 381 | dot: true, 382 | cwd: '<%%= config.app %>', 383 | dest: '<%%= config.dist %>', 384 | src: [ 385 | '*.{ico,png,txt}', 386 | 'images/{,*/}*.webp', 387 | '{,*/}*.html', 388 | 'fonts/{,*/}*.*' 389 | ] 390 | }<% if (includeBootstrap) { %>, { 391 | expand: true, 392 | dot: true, 393 | cwd: '<% if (includeSass) { 394 | %>.<% 395 | } else { 396 | %>bower_components/bootstrap/dist<% 397 | } %>', 398 | src: '<% if (includeSass) { 399 | %>bower_components/bootstrap-sass/assets/fonts/bootstrap/*<% 400 | } else { 401 | %>fonts/*<% 402 | } %>', 403 | dest: '<%%= config.dist %>' 404 | }<% } %>] 405 | }<% if (!includeSass) { %>, 406 | styles: { 407 | expand: true, 408 | dot: true, 409 | cwd: '<%%= config.app %>/styles', 410 | dest: '.tmp/styles/', 411 | src: '{,*/}*.css' 412 | }<% } %> 413 | }, 414 | 415 | // Run some tasks in parallel to speed up build process 416 | concurrent: { 417 | server: [<% if (useBabel) { %> 418 | 'babel:dist',<% } %><% if (includeSass) { %> 419 | 'sass'<% } else { %> 420 | 'copy:styles'<% } %> 421 | ], 422 | test: [<% if (useBabel) { %> 423 | 'babel'<% } %><% if (useBabel && !includeSass) { %>,<% } %><% if (!includeSass) { %> 424 | 'copy:styles'<% } %> 425 | ], 426 | dist: [<% if (useBabel) { %> 427 | 'babel',<% } %><% if (includeSass) { %> 428 | 'sass',<% } else { %> 429 | 'copy:styles',<% } %> 430 | 'imagemin', 431 | 'svgmin' 432 | ] 433 | } 434 | }); 435 | 436 | 437 | grunt.registerTask('serve', 'start the server and preview your app', function (target) { 438 | 439 | if (target === 'dist') { 440 | return grunt.task.run(['build', 'browserSync:dist']); 441 | } 442 | 443 | grunt.task.run([ 444 | 'clean:server', 445 | 'wiredep', 446 | 'concurrent:server', 447 | 'postcss', 448 | 'browserSync:livereload', 449 | 'watch' 450 | ]); 451 | }); 452 | 453 | grunt.registerTask('server', function (target) { 454 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 455 | grunt.task.run([target ? ('serve:' + target) : 'serve']); 456 | }); 457 | 458 | grunt.registerTask('test', function (target) { 459 | if (target !== 'watch') { 460 | grunt.task.run([ 461 | 'clean:server', 462 | 'concurrent:test', 463 | 'postcss' 464 | ]); 465 | } 466 | 467 | grunt.task.run([ 468 | 'browserSync:test',<% if (testFramework === 'mocha') { %> 469 | 'mocha'<% } else if (testFramework === 'jasmine') { %> 470 | 'jasmine'<% } %> 471 | ]); 472 | }); 473 | 474 | grunt.registerTask('build', [ 475 | 'clean:dist', 476 | 'wiredep', 477 | 'useminPrepare', 478 | 'concurrent:dist', 479 | 'postcss', 480 | 'concat', 481 | 'cssmin', 482 | 'uglify', 483 | 'copy:dist', 484 | 'filerev', 485 | 'usemin', 486 | 'htmlmin' 487 | ]); 488 | 489 | grunt.registerTask('default', [ 490 | 'newer:eslint', 491 | 'test', 492 | 'build' 493 | ]); 494 | }; 495 | --------------------------------------------------------------------------------